diff --git a/README.md b/README.md index 102770f..785f4a0 100644 --- a/README.md +++ b/README.md @@ -78,4 +78,4 @@ Antkeeper superbuild is licensed under the GNU General Public License, version 3 | Simple DirectMedia Layer | Sam Lantinga | Zlib | [SDL2/*](modules/SDL2/) | | stb_image | Sean Barrett | MIT / Public Domain | [stb_image.h](modules/stb/stb_image.h) | | stb_image_write | Sean Barrett | MIT / Public Domain | [stb_image_write.h](modules/stb/stb_image_write.h) | -| TinyEXR | Syoyo Fujita | BSD 3-clause | [tinyexr.h](modules/tinyexr/tinyexr.h) | +| TinyEXR | Syoyo Fujita | BSD-3-Clause | [tinyexr.h](modules/tinyexr/tinyexr.h) | diff --git a/modules/antkeeper-data b/modules/antkeeper-data index 5a11f8e..6d37070 160000 --- a/modules/antkeeper-data +++ b/modules/antkeeper-data @@ -1 +1 @@ -Subproject commit 5a11f8e3db0b7b014b99d91be196cea582e1688a +Subproject commit 6d3707094704ec12466068fd9c2c2fdc88a1da1b diff --git a/modules/antkeeper-source b/modules/antkeeper-source index 66f114d..96c678e 160000 --- a/modules/antkeeper-source +++ b/modules/antkeeper-source @@ -1 +1 @@ -Subproject commit 66f114dbf78bdbe42a5ea4905b0414c347ae45c5 +Subproject commit 96c678e5fc141df2440e8000d47dcf5cb942cd7c diff --git a/modules/entt/.clang-format b/modules/entt/.clang-format new file mode 100644 index 0000000..16d60ef --- /dev/null +++ b/modules/entt/.clang-format @@ -0,0 +1,41 @@ +BasedOnStyle: llvm +--- +AccessModifierOffset: -4 +AlignEscapedNewlines: DontAlign +AllowShortBlocksOnASingleLine: Empty +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLoopsOnASingleLine: true +AlwaysBreakTemplateDeclarations: Yes +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeTernaryOperators: true +ColumnLimit: 0 +DerivePointerAlignment: false +IncludeCategories: + - Regex: '<[[:alnum:]_]+>' + Priority: 1 + - Regex: '<(gtest|gmock)/' + Priority: 2 + - Regex: '<[[:alnum:]_./]+>' + Priority: 3 + - Regex: '") -message("*") - -option(USE_LIBCPP "Use libc++ by adding -stdlib=libc++ flag if availbale." ON) -option(USE_ASAN "Use address sanitizer by adding -fsanitize=address -fno-omit-frame-pointer flags" OFF) -option(USE_COMPILE_OPTIONS "Use compile options from EnTT." ON) +message(VERBOSE "*") +message(VERBOSE "* ${PROJECT_NAME} v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})") +message(VERBOSE "* Copyright (c) 2017-2022 Michele Caini ") +message(VERBOSE "*") # # Compiler stuff # -if(NOT MSVC AND USE_LIBCPP) - include(CheckCXXSourceCompiles) - include(CMakePushCheckState) +option(ENTT_USE_LIBCPP "Use libc++ by adding -stdlib=libc++ flag if available." OFF) +option(ENTT_USE_SANITIZER "Enable sanitizers by adding -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined flags if available." OFF) + +if(ENTT_USE_LIBCPP) + if(NOT WIN32) + include(CheckCXXSourceCompiles) + include(CMakePushCheckState) - cmake_push_check_state() + cmake_push_check_state() - set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -stdlib=libc++") + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -stdlib=libc++") - check_cxx_source_compiles(" - #include - int main() { return std::is_same_v ? 0 : 1; } - " HAS_LIBCPP) + check_cxx_source_compiles(" + #include + int main() { return std::is_same_v; } + " ENTT_HAS_LIBCPP) - if(NOT HAS_LIBCPP) - message(WARNING "The option USE_LIBCPP is set (by default) but libc++ is not available. The flag will not be added to the target.") + cmake_pop_check_state() endif() - cmake_pop_check_state() + if(NOT ENTT_HAS_LIBCPP) + message(VERBOSE "The option ENTT_USE_LIBCPP is set but libc++ is not available. The flag will not be added to the target.") + endif() +endif() + +if(ENTT_USE_SANITIZER) + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + set(ENTT_HAS_SANITIZER TRUE CACHE BOOL "" FORCE) + mark_as_advanced(ENTT_HAS_SANITIZER) + endif() + + if(NOT ENTT_HAS_SANITIZER) + message(VERBOSE "The option ENTT_USE_SANITIZER is set but sanitizer support is not available. The flags will not be added to the target.") + endif() endif() # # Add EnTT target # -add_library(EnTT INTERFACE) +option(ENTT_INCLUDE_HEADERS "Add all EnTT headers to the EnTT target." OFF) +option(ENTT_INCLUDE_NATVIS "Add EnTT natvis files to the EnTT target." OFF) + +if(ENTT_INCLUDE_NATVIS) + if(MSVC) + set(ENTT_HAS_NATVIS TRUE CACHE BOOL "" FORCE) + mark_as_advanced(ENTT_HAS_NATVIS) + endif() -configure_file(${EnTT_SOURCE_DIR}/cmake/in/version.h.in ${EnTT_SOURCE_DIR}/src/entt/config/version.h @ONLY) + if(NOT ENTT_HAS_NATVIS) + message(VERBOSE "The option ENTT_INCLUDE_NATVIS is set but natvis files are not supported. They will not be added to the target.") + endif() +endif() -target_include_directories( - EnTT INTERFACE - $ - $ -) +include(GNUInstallDirs) + +add_library(EnTT INTERFACE) +add_library(EnTT::EnTT ALIAS EnTT) -target_compile_definitions( +target_include_directories( EnTT - INTERFACE $<$,$>>:DEBUG> - INTERFACE $<$,$>>:RELEASE> + INTERFACE + $ + $ ) -if(USE_ASAN) - target_compile_options(EnTT INTERFACE $<$,$>>:-fsanitize=address -fno-omit-frame-pointer>) - target_link_libraries(EnTT INTERFACE $<$,$>>:-fsanitize=address -fno-omit-frame-pointer>) +target_compile_features(EnTT INTERFACE cxx_std_17) + +if(ENTT_INCLUDE_HEADERS) + target_sources( + EnTT + INTERFACE + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + ) endif() -if(USE_COMPILE_OPTIONS) - target_compile_options( +if(ENTT_HAS_NATVIS) + target_sources( EnTT - INTERFACE $<$,$>>:-O0 -g> - # it seems that -O3 ruins a bit the performance when using clang ... - INTERFACE $<$,$>:-O2> - # ... on the other side, GCC is incredibly comfortable with it. - INTERFACE $<$,$>:-O3> + INTERFACE + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ ) endif() -if(HAS_LIBCPP) - target_compile_options(EnTT BEFORE INTERFACE -stdlib=libc++) +if(ENTT_HAS_SANITIZER) + target_compile_options(EnTT INTERFACE $<$:-fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined>) + target_link_libraries(EnTT INTERFACE $<$:-fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined>) endif() -target_compile_features(EnTT INTERFACE cxx_std_17) +if(ENTT_HAS_LIBCPP) + target_compile_options(EnTT BEFORE INTERFACE -stdlib=libc++) +endif() # -# Install EnTT +# Install pkg-config file # -if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - set(CUSTOM_INSTALL_CONFIGDIR cmake) -else() - set(CUSTOM_INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/entt) -endif() - -install(DIRECTORY src/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -install(TARGETS EnTT EXPORT EnTTTargets) +set(EnTT_PKGCONFIG ${CMAKE_CURRENT_BINARY_DIR}/entt.pc) -export(EXPORT EnTTTargets FILE ${EnTT_BINARY_DIR}/EnTTTargets.cmake) +configure_file( + ${EnTT_SOURCE_DIR}/cmake/in/entt.pc.in + ${EnTT_PKGCONFIG} + @ONLY +) install( - EXPORT EnTTTargets - FILE EnTTTargets.cmake - DESTINATION ${CUSTOM_INSTALL_CONFIGDIR} + FILES ${EnTT_PKGCONFIG} + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig ) # -# Build tree package config file +# Install EnTT # -configure_file(cmake/in/EnTTBuildConfig.cmake.in EnTTConfig.cmake @ONLY) - include(CMakePackageConfigHelpers) -# -# Install tree package config file -# - -configure_package_config_file( - cmake/in/EnTTConfig.cmake.in - ${CUSTOM_INSTALL_CONFIGDIR}/EnTTConfig.cmake - INSTALL_DESTINATION ${CUSTOM_INSTALL_CONFIGDIR} - PATH_VARS CMAKE_INSTALL_INCLUDEDIR +install( + TARGETS EnTT + EXPORT EnTTTargets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ) write_basic_package_version_file( - ${EnTT_BINARY_DIR}/EnTTConfigVersion.cmake + EnTTConfigVersion.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion ) +configure_package_config_file( + ${EnTT_SOURCE_DIR}/cmake/in/EnTTConfig.cmake.in + EnTTConfig.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/EnTT/cmake +) + +export( + EXPORT EnTTTargets + FILE ${CMAKE_CURRENT_BINARY_DIR}/EnTTTargets.cmake + NAMESPACE EnTT:: +) + +install( + EXPORT EnTTTargets + FILE EnTTTargets.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/EnTT/cmake + NAMESPACE EnTT:: +) + install( FILES - ${EnTT_BINARY_DIR}/${CUSTOM_INSTALL_CONFIGDIR}/EnTTConfig.cmake - ${EnTT_BINARY_DIR}/EnTTConfigVersion.cmake - DESTINATION ${CUSTOM_INSTALL_CONFIGDIR} + ${PROJECT_BINARY_DIR}/EnTTConfig.cmake + ${PROJECT_BINARY_DIR}/EnTTConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/EnTT/cmake ) +install(DIRECTORY src/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + export(PACKAGE EnTT) # # Tests # -option(BUILD_TESTING "Enable testing with ctest." OFF) - -if(BUILD_TESTING) - set(THREADS_PREFER_PTHREAD_FLAG ON) - find_package(Threads REQUIRED) +option(ENTT_BUILD_TESTING "Enable building tests." OFF) - option(FIND_GTEST_PACKAGE "Enable finding gtest package." OFF) - - if(FIND_GTEST_PACKAGE) - find_package(GTest REQUIRED) - else() - # gtest, gtest_main, gmock and gmock_main targets are available from now on - set(GOOGLETEST_DEPS_DIR ${EnTT_SOURCE_DIR}/deps/googletest) - configure_file(${EnTT_SOURCE_DIR}/cmake/in/googletest.in ${GOOGLETEST_DEPS_DIR}/CMakeLists.txt) - execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${GOOGLETEST_DEPS_DIR}) - execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${GOOGLETEST_DEPS_DIR}) - set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) - add_subdirectory(${GOOGLETEST_DEPS_DIR}/src ${GOOGLETEST_DEPS_DIR}/build) - target_compile_features(gmock_main PRIVATE $) - target_compile_features(gmock PRIVATE $) - add_library(GTest::Main ALIAS gtest_main) - endif() +if(ENTT_BUILD_TESTING) + option(ENTT_FIND_GTEST_PACKAGE "Enable finding gtest package." OFF) + option(ENTT_BUILD_BENCHMARK "Build benchmark." OFF) + option(ENTT_BUILD_EXAMPLE "Build examples." OFF) + option(ENTT_BUILD_LIB "Build lib tests." OFF) + option(ENTT_BUILD_SNAPSHOT "Build snapshot test with Cereal." OFF) - option(BUILD_BENCHMARK "Build benchmark." OFF) - option(BUILD_LIB "Build lib example." OFF) - option(BUILD_MOD "Build mod example." OFF) - option(BUILD_SNAPSHOT "Build snapshot example." OFF) + set(ENTT_ID_TYPE std::uint32_t CACHE STRING "Type of identifiers to use for the tests") + set(ENTT_CXX_STD cxx_std_17 CACHE STRING "C++ standard revision to use for the tests") + include(CTest) enable_testing() add_subdirectory(test) endif() @@ -199,9 +299,9 @@ endif() # Documentation # -option(BUILD_DOCS "Enable building with documentation." OFF) +option(ENTT_BUILD_DOCS "Enable building with documentation." OFF) -if(BUILD_DOCS) +if(ENTT_BUILD_DOCS) find_package(Doxygen 1.8) if(DOXYGEN_FOUND) @@ -214,13 +314,16 @@ endif() # add_custom_target( - entt_aob + aob SOURCES - appveyor.yml + .github/workflows/build.yml + .github/workflows/coverage.yml + .github/workflows/deploy.yml + .github/workflows/sanitizer.yml + .github/FUNDING.yml AUTHORS CONTRIBUTING.md LICENSE README.md TODO - .travis.yml ) diff --git a/modules/entt/CONTRIBUTING.md b/modules/entt/CONTRIBUTING.md index 6795dca..117ede6 100644 --- a/modules/entt/CONTRIBUTING.md +++ b/modules/entt/CONTRIBUTING.md @@ -1,7 +1,7 @@ # Contributing First of all, thank you very much for taking the time to contribute to the -`EnTT` framework.
+`EnTT` library.
How to do it mostly depends on the type of contribution: * If you have a question, **please** ensure there isn't already an answer for @@ -28,7 +28,7 @@ How to do it mostly depends on the type of contribution: * If you found a bug and you wrote a patch to fix it, open a new [pull request](https://github.com/skypjack/entt/pulls) with your code. **Please**, add some tests to avoid regressions in future if possible, it - would be really appreciated. Note that the `EnTT` framework has a + would be really appreciated. Note that the `EnTT` library has a [coverage at 100%](https://coveralls.io/github/skypjack/entt?branch=master) (at least it was at 100% at the time I wrote this file) and this is the reason for which you can be confident with using it in a production environment. diff --git a/modules/entt/LICENSE b/modules/entt/LICENSE index 7951502..770161e 100644 --- a/modules/entt/LICENSE +++ b/modules/entt/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2017-2019 Michele Caini +Copyright (c) 2017-2022 Michele Caini, author of EnTT Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/modules/entt/README.md b/modules/entt/README.md index 4ccbc6c..3f6b342 100644 --- a/modules/entt/README.md +++ b/modules/entt/README.md @@ -1,39 +1,47 @@ -![EnTT: Gaming meets modern C++](https://user-images.githubusercontent.com/1812216/42513718-ee6e98d0-8457-11e8-9baf-8d83f61a3097.png) +![EnTT: Gaming meets modern C++](https://user-images.githubusercontent.com/1812216/103550016-90752280-4ea8-11eb-8667-12ed2219e137.png) -[![GitHub version](https://badge.fury.io/gh/skypjack%2Fentt.svg)](http://badge.fury.io/gh/skypjack%2Fentt) -[![LoC](https://tokei.rs/b1/github/skypjack/entt)](https://github.com/skypjack/entt) -[![Build Status](https://travis-ci.org/skypjack/entt.svg?branch=master)](https://travis-ci.org/skypjack/entt) -[![Build status](https://ci.appveyor.com/api/projects/status/rvhaabjmghg715ck?svg=true)](https://ci.appveyor.com/project/skypjack/entt) -[![Coverage Status](https://coveralls.io/repos/github/skypjack/entt/badge.svg?branch=master)](https://coveralls.io/github/skypjack/entt?branch=master) +[![Build Status](https://github.com/skypjack/entt/workflows/build/badge.svg)](https://github.com/skypjack/entt/actions) +[![Coverage](https://codecov.io/gh/skypjack/entt/branch/master/graph/badge.svg)](https://codecov.io/gh/skypjack/entt) +[![Try online](https://img.shields.io/badge/try-online-brightgreen)](https://godbolt.org/z/zxW73f) +[![Documentation](https://img.shields.io/badge/docs-docsforge-blue)](http://entt.docsforge.com/) [![Gitter chat](https://badges.gitter.im/skypjack/entt.png)](https://gitter.im/skypjack/entt) -[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/skypjack) +[![Discord channel](https://img.shields.io/discord/707607951396962417?logo=discord)](https://discord.gg/5BjPWBd) +[![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://www.paypal.me/skypjack) + +> `EnTT` has been a dream so far, we haven't found a single bug to date and it's +> super easy to work with `EnTT` is a header-only, tiny and easy to use library for game programming and -much more written in **modern C++**, mainly known for its innovative -**entity-component-system (ECS)** model.
+much more written in **modern C++**.
[Among others](https://github.com/skypjack/entt/wiki/EnTT-in-Action), it's used in [**Minecraft**](https://minecraft.net/en-us/attribution/) by Mojang, the [**ArcGIS Runtime SDKs**](https://developers.arcgis.com/arcgis-runtime/) by Esri -and [**The Forge**](https://github.com/ConfettiFX/The-Forge) by Confetti. Read -on to find out what it can offer you. +and the amazing [**Ragdoll**](https://ragdolldynamics.com/).
+If you don't see your project in the list, please open an issue, submit a PR or +add the [#entt](https://github.com/topics/entt) tag to your _topics_! :+1: --- Do you want to **keep up with changes** or do you have a **question** that doesn't require you to open an issue?
-Join the [gitter channel](https://gitter.im/skypjack/entt) and meet other users -like you. The more we are, the better for everyone. - -If you use `EnTT` and you want to say thanks or support the project, please -**consider becoming a patron**: - -[![Patreon](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/bePatron?c=1772573) - -[Many thanks](https://skypjack.github.io/patreon/) to those who supported me and -still support me today. +Join the [gitter channel](https://gitter.im/skypjack/entt) and the +[discord server](https://discord.gg/5BjPWBd), meet other users like you. The +more we are, the better for everyone.
+Don't forget to check the +[FAQs](https://github.com/skypjack/entt/wiki/Frequently-Asked-Questions) and the +[wiki](https://github.com/skypjack/entt/wiki) too. Your answers may already be +there. + +Do you want to support `EnTT`? Consider becoming a +[**sponsor**](https://github.com/users/skypjack/sponsorship). +Many thanks to [these people](https://skypjack.github.io/sponsorship/) and +**special** thanks to: + +[![mojang](https://user-images.githubusercontent.com/1812216/106253145-67ca1980-6217-11eb-9c0b-d93561b37098.png)](https://mojang.com) +[![imgly](https://user-images.githubusercontent.com/1812216/106253726-271ed000-6218-11eb-98e0-c9c681925770.png)](https://img.ly/) # Table of Contents @@ -41,19 +49,17 @@ still support me today. * [Code Example](#code-example) * [Motivation](#motivation) * [Performance](#performance) -* [Build Instructions](#build-instructions) +* [Integration](#integration) * [Requirements](#requirements) - * [Library](#library) - * [Documentation](#documentation) - * [Tests](#tests) -* [Packaging Tools](#packaging-tools) + * [CMake](#cmake) + * [Natvis support](#natvis-support) + * [Packaging Tools](#packaging-tools) + * [pkg-config](#pkg-config) +* [Documentation](#documentation) +* [Tests](#tests) * [EnTT in Action](#entt-in-action) * [Contributors](#contributors) * [License](#license) -* [Support](#support) - * [Patreon](#patreon) - * [Donation](#donation) - * [Hire me](#hire-me) @@ -71,42 +77,40 @@ This project started off as a pure entity-component system. Over time the codebase has grown as more and more classes and functionalities were added.
Here is a brief, yet incomplete list of what it offers today: -* Statically generated integer **identifiers for types** (assigned either at - compile-time or at runtime). -* A `constexpr` utility for **human readable resource identifiers**. -* A minimal **configuration system** built using the monostate pattern. -* An incredibly fast **entity-component system** based on sparse sets, with its - own _pay for what you use_ policy to adjust performance and memory usage - according to the users' requirements. +* Built-in **RTTI system** mostly similar to the standard one. +* A `constexpr` utility for human readable **resource names**. +* Minimal **configuration system** built using the monostate pattern. +* Incredibly fast **entity-component system** with its own _pay for what you + use_ policy. * Views and groups to iterate entities and components and allow different access patterns, from **perfect SoA** to fully random. -* A lot of **facilities** built on top of the entity-component system to support - the users and avoid reinventing the wheel (dependencies, snapshot, actor class - for those who aren't confident with the architecture and so on). +* A lot of **facilities** built on top of the entity-component system to help + the users and avoid reinventing the wheel. * The smallest and most basic implementation of a **service locator** ever seen. -* A built-in, non-intrusive and macro-free **runtime reflection system**. +* A built-in, non-intrusive and macro-free runtime **reflection system**. +* **Static polymorphism** made simple and within everyone's reach. +* A few homemade containers, like a sparse set based **hash map**. * A **cooperative scheduler** for processes of any type. * All that is needed for **resource management** (cache, loaders, handles). -* **Delegates**, **signal handlers** (with built-in support for collectors) and - a tiny **event dispatcher** for immediate and delayed events to integrate in - loops. +* Delegates, **signal handlers** and a tiny event dispatcher. * A general purpose **event emitter** as a CRTP idiom based class template. * And **much more**! Check out the [**wiki**](https://github.com/skypjack/entt/wiki). -Consider this list a work in progress as well as the project. The whole API is -fully documented in-code for those who are brave enough to read it. +Consider these lists a work in progress as well as the project. The whole API is +fully documented in-code for those who are brave enough to read it.
+Please, do note that all tools are also DLL-friendly now and run smoothly across +boundaries. -Currently, `EnTT` is tested on Linux, Microsoft Windows and OSX. It has proven -to work also on both Android and iOS.
-Most likely it won't be problematic on other systems as well, but it hasn't been -sufficiently tested so far. +One thing known to most is that `EnTT` is also used in **Minecraft**.
+Given that the game is available literally everywhere, I can confidently say +that the library has been sufficiently tested on every platform that can come to +mind. ## Code Example ```cpp #include -#include struct position { float x; @@ -119,52 +123,43 @@ struct velocity { }; void update(entt::registry ®istry) { - auto view = registry.view(); + auto view = registry.view(); - for(auto entity: view) { - // gets only the components that are going to be used ... + // use a callback + view.each([](const auto &pos, auto &vel) { /* ... */ }); - auto &vel = view.get(entity); - - vel.dx = 0.; - vel.dy = 0.; + // use an extended callback + view.each([](const auto entity, const auto &pos, auto &vel) { /* ... */ }); + // use a range-for + for(auto [entity, pos, vel]: view.each()) { // ... } -} - -void update(std::uint64_t dt, entt::registry ®istry) { - registry.view().each([dt](auto &pos, auto &vel) { - // gets all the components of the view at once ... - - pos.x += vel.dx * dt; - pos.y += vel.dy * dt; + // use forward iterators and get only the components of interest + for(auto entity: view) { + auto &vel = view.get(entity); // ... - }); + } } int main() { entt::registry registry; - std::uint64_t dt = 16; - for(auto i = 0; i < 10; ++i) { - auto entity = registry.create(); - registry.assign(entity, i * 1.f, i * 1.f); - if(i % 2 == 0) { registry.assign(entity, i * .1f, i * .1f); } + for(auto i = 0u; i < 10u; ++i) { + const auto entity = registry.create(); + registry.emplace(entity, i * 1.f, i * 1.f); + if(i % 2 == 0) { registry.emplace(entity, i * .1f, i * .1f); } } - update(dt, registry); update(registry); - - // ... } ``` ## Motivation I started developing `EnTT` for the _wrong_ reason: my goal was to design an -entity-component system to beat another well known open source solution both in +entity-component system to beat another well known open source library both in terms of performance and possibly memory usage.
In the end, I did it, but it wasn't very satisfying. Actually it wasn't satisfying at all. The fastest and nothing more, fairly little indeed. When I @@ -177,69 +172,37 @@ amazing set of features. And even more, of course. ## Performance -As it stands right now, `EnTT` is just fast enough for my requirements when -compared to my first choice (it was already amazingly fast actually).
-Below is a comparison between the two (both of them compiled with GCC 7.3.0 on a -Dell XPS 13 from mid 2014): - -| Benchmark | EntityX (compile-time) | EnTT | -|-----------|-------------|-------------| -| Create 1M entities | 0.0147s | **0.0046s** | -| Destroy 1M entities | 0.0053s | **0.0045s** | -| 1M entities, one component | 0.0012s | **1.9e-07s** | -| 1M entities, two components | 0.0012s | **3.8e-07s** | -| 1M entities, two components
Half of the entities have all the components | 0.0009s | **3.8e-07s** | -| 1M entities, two components
One of the entities has all the components | 0.0008s | **1.0e-06s** | -| 1M entities, five components | 0.0010s | **7.0e-07s** | -| 1M entities, ten components | 0.0011s | **1.2e-06s** | -| 1M entities, ten components
Half of the entities have all the components | 0.0010s | **1.2e-06s** | -| 1M entities, ten components
One of the entities has all the components | 0.0008s | **1.2e-06s** | -| Sort 150k entities, one component
Arrays are in reverse order | - | **0.0036s** | -| Sort 150k entities, enforce permutation
Arrays are in reverse order | - | **0.0005s** | -| Sort 150k entities, one component
Arrays are almost sorted, std::sort | - | **0.0035s** | -| Sort 150k entities, one component
Arrays are almost sorted, insertion sort | - | **0.0007s** | - -Note: The default version of `EntityX` (`master` branch) wasn't added to the -comparison because it's already much slower than its compile-time counterpart. - -Pretty interesting results, aren't them? In fact, these benchmarks are the ones -used by `EntityX` to show _how fast it is_. To be honest, they aren't so good -and these results shouldn't be taken too seriously (indeed they are completely -unrealistic).
-The proposed entity-component system is incredibly fast to iterate entities, -this is a fact. The compiler can make a lot of optimizations because of how -`EnTT` works, even more when components aren't used at all. This is exactly the -case for these benchmarks. On the other hand, if we consider real world cases, -`EnTT` is somewhere between a bit and much faster than the other solutions -around when users also access the components and not just the entities, although -it isn't as fast as reported by these benchmarks.
-This is why they are completely wrong and cannot be used to evaluate any of the -entity-component-system libraries out there. +The proposed entity-component system is incredibly fast to iterate entities and +components, this is a fact. Some compilers make a lot of optimizations because +of how `EnTT` works, some others aren't that good. In general, if we consider +real world cases, `EnTT` is somewhere between a bit and much faster than many of +the other solutions around, although I couldn't check them all for obvious +reasons. + +If you are interested, you can compile the `benchmark` test in release mode (to +enable compiler optimizations, otherwise it would make little sense) by setting +the `ENTT_BUILD_BENCHMARK` option of `CMake` to `ON`, then evaluate yourself +whether you're satisfied with the results or not. + +Honestly I got tired of updating the README file whenever there is an +improvement.
+There are already a lot of projects out there that use `EnTT` as a basis for +comparison (this should already tell you a lot). Many of these benchmarks are +completely wrong, many others are simply incomplete, good at omitting some +information and using the wrong function to compare a given feature. Certainly +there are also good ones but they age quickly if nobody updates them, especially +when the library they are dealing with is actively developed. The choice to use `EnTT` should be based on its carefully designed API, its -set of features and the general performance, not because some single benchmark -shows it to be the fastest tool available. +set of features and the general performance, **not** because some single +benchmark shows it to be the fastest tool available. In the future I'll likely try to get even better performance while still adding new features, mainly for fun.
If you want to contribute and/or have suggestions, feel free to make a PR or open an issue to discuss your idea. -# Build Instructions - -## Requirements - -To be able to use `EnTT`, users must provide a full-featured compiler that -supports at least C++17.
-The requirements below are mandatory to compile the tests and to extract the -documentation: - -* CMake version 3.2 or later. -* Doxygen version 1.8 or later. - -If you are looking for a C++14 version of `EnTT`, check out the git tag `cpp14`. - -## Library +# Integration `EnTT` is a header-only library. This means that including the `entt.hpp` header is enough to include the library as a whole and use it. For those who are @@ -260,13 +223,120 @@ Use the line below to include only the entity-component system instead: Then pass the proper `-I` argument to the compiler to add the `src` directory to the include paths. -## Documentation +## Requirements + +To be able to use `EnTT`, users must provide a full-featured compiler that +supports at least C++17.
+The requirements below are mandatory to compile the tests and to extract the +documentation: + +* `CMake` version 3.7 or later. +* `Doxygen` version 1.8 or later. + +Alternatively, [Bazel](https://bazel.build) is also supported as a build system +(credits to [zaucy](https://github.com/zaucy) who offered to maintain it).
+In the documentation below I'll still refer to `CMake`, this being the official +build system of the library. + +## CMake + +To use `EnTT` from a `CMake` project, just link an existing target to the +`EnTT::EnTT` alias.
+The library offers everything you need for locating (as in `find_package`), +embedding (as in `add_subdirectory`), fetching (as in `FetchContent`) or using +it in many of the ways that you can think of and that involve `CMake`.
+Covering all possible cases would require a treaty and not a simple README file, +but I'm confident that anyone reading this section also knows what it's about +and can use `EnTT` from a `CMake` project without problems. + +## Natvis support + +When using `CMake`, just enable the option `ENTT_INCLUDE_NATVIS` and enjoy +it.
+Otherwise, most of the tools are covered via Natvis and all files can be found +in the `natvis` directory, divided by module.
+If you spot errors or have suggestions, any contribution is welcome! + +## Packaging Tools + +`EnTT` is available for some of the most known packaging tools. In particular: + +* [`Conan`](https://github.com/conan-io/conan-center-index), the C/C++ Package + Manager for Developers. + +* [`vcpkg`](https://github.com/Microsoft/vcpkg), Microsoft VC++ Packaging + Tool.
+ You can download and install `EnTT` in just a few simple steps: + + ``` + $ git clone https://github.com/Microsoft/vcpkg.git + $ cd vcpkg + $ ./bootstrap-vcpkg.sh + $ ./vcpkg integrate install + $ vcpkg install entt + ``` -The documentation is based on [doxygen](http://www.doxygen.nl/). -To build it: + Or you can use the `experimental` feature to test the latest changes: + + ``` + vcpkg install entt[experimental] --head + ``` + + The `EnTT` port in `vcpkg` is kept up to date by Microsoft team members and + community contributors.
+ If the version is out of date, please + [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the + `vcpkg` repository. + +* [`Homebrew`](https://github.com/skypjack/homebrew-entt), the missing package + manager for macOS.
+ Available as a homebrew formula. Just type the following to install it: + + ``` + brew install skypjack/entt/entt + ``` + +* [`build2`](https://build2.org), build toolchain for developing and packaging C + and C++ code.
+ In order to use the [`entt`](https://cppget.org/entt) package in a `build2` + project, add the following line or a similar one to the `manifest` file: + + ``` + depends: entt ^3.0.0 + ``` + + Also check that the configuration refers to a valid repository, so that the + package can be found by `build2`: + + * [`cppget.org`](https://cppget.org), the open-source community central + repository, accessible as `https://pkg.cppget.org/1/stable`. + + * [Package source repository](https://github.com/build2-packaging/entt): + accessible as either `https://github.com/build2-packaging/entt.git` or + `ssh://git@github.com/build2-packaging/entt.git`. + Feel free to [report issues](https://github.com/build2-packaging/entt) with + this package. + + Both can be used with `bpkg add-repo` or added in a project + `repositories.manifest`. See the official + [documentation](https://build2.org/build2-toolchain/doc/build2-toolchain-intro.xhtml#guide-repositories) + for more details. + +Consider this list a work in progress and help me to make it longer if you like. + +## pkg-config + +`EnTT` also supports `pkg-config` (for some definition of _supports_ at least). +A suitable file called `entt.pc` is generated and installed in a proper +directory when running `CMake`.
+This should also make it easier to use with tools such as `Meson` or similar. + +# Documentation + +The documentation is based on [doxygen](http://www.doxygen.nl/). To build it: $ cd build - $ cmake .. -DBUILD_DOCS=ON + $ cmake .. -DENTT_BUILD_DOCS=ON $ make The API reference will be created in HTML format within the directory @@ -278,46 +348,33 @@ The API reference will be created in HTML format within the directory -It's also available [online](https://skypjack.github.io/entt/) for the latest -version.
-Finally, there exists a [wiki](https://github.com/skypjack/entt/wiki) dedicated +The same version is also available [online](https://skypjack.github.io/entt/) +for the latest release, that is the last stable tag. If you are looking for +something more pleasing to the eye, consider reading the nice-looking version +available on [docsforge](https://entt.docsforge.com/): same documentation, much +more pleasant to read.
+Moreover, there exists a [wiki](https://github.com/skypjack/entt/wiki) dedicated to the project where users can find all related documentation pages. -## Tests +# Tests To compile and run the tests, `EnTT` requires *googletest*.
`cmake` will download and compile the library before compiling anything else. -In order to build the tests, set the CMake option `BUILD_TESTING` to `ON`. +In order to build the tests, set the `CMake` option `ENTT_BUILD_TESTING` to +`ON`. To build the most basic set of tests: * `$ cd build` -* `$ cmake -DBUILD_TESTING=ON ..` +* `$ cmake -DENTT_BUILD_TESTING=ON ..` * `$ make` * `$ make test` Note that benchmarks are not part of this set. -# Packaging Tools - -`EnTT` is available for some of the most known packaging tools. In particular: - -* [`Conan`](https://bintray.com/skypjack/conan/entt%3Askypjack/_latestVersion), - the C/C++ Package Manager for Developers. -* [`Homebrew`](https://github.com/skypjack/homebrew-entt), the missing package - manager for macOS.
- Available as a homebrew formula. Just type the following to install it: - ``` - brew install skypjack/entt/entt - ``` -* [`vcpkg`](https://github.com/Microsoft/vcpkg/tree/master/ports/entt), - Microsoft VC++ Packaging Tool. - -Consider this list a work in progress and help me to make it longer. - @@ -338,18 +395,14 @@ open an issue or a PR and I'll be glad to add them to the list. # Contributors -`EnTT` was written initially as a faster alternative to other well known and -open source entity-component systems. Nowadays this library is moving its first -steps. Much more will come in the future and hopefully I'm going to work on it -for a long time.
-Requests for features, PR, suggestions ad feedback are highly appreciated. +Requests for features, PRs, suggestions ad feedback are highly appreciated. -If you find you can help me and want to contribute to the project with your -experience or you do want to get part of the project for some other reasons, -feel free to contact me directly (you can find the mail in the +If you find you can help and want to contribute to the project with your +experience or you do want to get part of the project for some other reason, feel +free to contact me directly (you can find the mail in the [profile](https://github.com/skypjack)).
I can't promise that each and every contribution will be accepted, but I can -assure that I'll do my best to take them all seriously. +assure that I'll do my best to take them all as soon as possible. If you decide to participate, please see the guidelines for [contributing](CONTRIBUTING.md) before to create issues or pull @@ -363,49 +416,12 @@ know who has participated so far. # License -Code and documentation Copyright (c) 2017-2019 Michele Caini.
-Logo Copyright (c) 2018-2019 Richard Caseres. +Code and documentation Copyright (c) 2017-2022 Michele Caini.
+Colorful logo Copyright (c) 2018-2021 Richard Caseres. Code released under -[the MIT license](https://github.com/skypjack/entt/blob/master/LICENSE). +[the MIT license](https://github.com/skypjack/entt/blob/master/LICENSE).
Documentation released under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
-Logo released under +All logos released under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). - - -# Support - -## Patreon - -Become a [patron](https://www.patreon.com/bePatron?c=1772573) and get access to -extra content, help me spend more time on the projects you love and create new -ones for you. Your support will help me to continue the work done so far and -make it more professional and feature-rich every day.
-It takes very little to -[become a patron](https://www.patreon.com/bePatron?c=1772573) and thus help the -software you use every day. Don't miss the chance. - -## Donation - -Developing and maintaining `EnTT` takes some time and lots of coffee. I'd like -to add more and more functionalities in future and turn it in a full-featured -solution.
-If you want to support this project, you can offer me an espresso. I'm from -Italy, we're used to turning the best coffee ever in code. If you find that -it's not enough, feel free to support me the way you prefer.
-Take a look at the donation button at the top of the page for more details or -just click [here](https://www.paypal.me/skypjack). - -## Hire me - -If you start using `EnTT` and need help, if you want a new feature and want me -to give it the highest priority, if you have any other reason to contact me: -do not hesitate. I'm available for hiring.
-Feel free to take a look at my [profile](https://github.com/skypjack) and -contact me by mail. - diff --git a/modules/entt/TODO b/modules/entt/TODO index ffb19fa..140bca9 100644 --- a/modules/entt/TODO +++ b/modules/entt/TODO @@ -1,25 +1,26 @@ -* long term feature: templated generic vm -* long term feature: shared_ptr less locator -* long term feature: shared_ptr less resource cache -* custom allocators and EnTT allocator-aware in general (long term feature, I don't actually need it at the moment) - see #22 * debugging tools (#60): the issue online already contains interesting tips on this, look at it -* runner proposal: https://en.wikipedia.org/wiki/Fork%E2%80%93join_model https://slide-rs.github.io/specs/03_dispatcher.html -* work stealing job system (see #100) -* meta: sort of meta view based on meta stuff to iterate entities, void * and meta info objects -* allow for built-in parallel each if possible -* allow to replace std:: with custom implementations -* remove runtime views, welcome reflection and what about snapshot? -* empty components model allows for shared components and prefabs unity-like - - each with entity return the shared component multiple times, one per entity that refers to it - - each components only return actual component, so shared components are returned only once -* types defined at runtime that refer to the same compile-time type (but to different pools) are possible, the library is almost there -* add take functionality, eg registry.take(entity, other); where it takes the entity and all its components from registry and move them to other -* add opaque input iterators to views and groups that return tuples (proxy), multi-pass guaranteed -* add fast lane for raw iterations, extend mt doc to describe allowed add/remove with pre-allocations on fast lanes -* review 64 bit id: user defined area + dedicated member on the registry to set it -* early out in views using bitmasks with bloom filter like access based on modulus - - standard each, use bitmask to speed up the whole thing and avoid accessing the pools to test for the page - - iterator based each with a couple of iterators passed from outside (use bitmask + has) -* stable component handle that isn't affected by reallocations -* multi component registry::remove and some others? -* reactive systems +* work stealing job system (see #100) + mt scheduler based on const awareness for types +* add examples (and credits) from @alanjfs :) + +EXAMPLES +* filter on runtime values/variables (not only types) +* support to polymorphic types (see #859) + +WIP: +* view/group: no storage_traits dependency -> use storage instead of components for the definition +* basic_storage::bind for cross-registry setups +* uses-allocator construction: any (with allocator support), poly, ... +* process scheduler: reviews, use free lists internally +* iterator based try_emplace vs try_insert for perf reasons +* dedicated entity storage, in-place O(1) release/destroy for non-orphaned entities, out-of-sync model +* entity-only and exclude-only views +* custom allocators all over (sigh storage mixin, registry, ...) +* consider removing ENTT_NOEXCEPT, use ENTT_NOEXCEPT_IF (or noexcept(...)) as appropriate in any case (ie make compressed_pair conditionally noexcept) +* add test for maximum number of entities reached + +WIP: +* add user data to type_info +* write documentation for custom storages and views!! +* make runtime views use opaque storage and therefore return also elements. +* entity-aware observer, add observer functions aside observer class +* deprecate non-owning groups in favor of owning views and view packs, introduce lazy owning views diff --git a/modules/entt/WORKSPACE b/modules/entt/WORKSPACE new file mode 100644 index 0000000..340fe8f --- /dev/null +++ b/modules/entt/WORKSPACE @@ -0,0 +1 @@ +workspace(name = "com_github_skypjack_entt") diff --git a/modules/entt/appveyor.yml b/modules/entt/appveyor.yml deleted file mode 100644 index 03e6493..0000000 --- a/modules/entt/appveyor.yml +++ /dev/null @@ -1,25 +0,0 @@ -# can use variables like {build} and {branch} -version: 1.0.{build} - -image: Visual Studio 2017 - -environment: - BUILD_DIR: "%APPVEYOR_BUILD_FOLDER%\\build" - -platform: - - Win32 - -configuration: - - Release - -before_build: - - cd %BUILD_DIR% - - cmake .. -DBUILD_TESTING=ON -DBUILD_LIB=ON -DCMAKE_CXX_FLAGS=/W1 -G"Visual Studio 15 2017" - -after_build: - - ctest -C Release -j4 - -build: - parallel: true - project: build/entt.sln - verbosity: minimal diff --git a/modules/entt/cmake/in/EnTTBuildConfig.cmake.in b/modules/entt/cmake/in/EnTTBuildConfig.cmake.in deleted file mode 100644 index 4b6ad4f..0000000 --- a/modules/entt/cmake/in/EnTTBuildConfig.cmake.in +++ /dev/null @@ -1,6 +0,0 @@ -set(ENTT_VERSION "@PROJECT_VERSION@") -set(ENTT_INCLUDE_DIRS "@CMAKE_CURRENT_SOURCE_DIR@/src") - -if(NOT CMAKE_VERSION VERSION_LESS "3.0") - include("${CMAKE_CURRENT_LIST_DIR}/EnTTTargets.cmake") -endif() diff --git a/modules/entt/cmake/in/EnTTConfig.cmake.in b/modules/entt/cmake/in/EnTTConfig.cmake.in index 069242b..f34782e 100644 --- a/modules/entt/cmake/in/EnTTConfig.cmake.in +++ b/modules/entt/cmake/in/EnTTConfig.cmake.in @@ -1,11 +1,5 @@ -set(ENTT_VERSION "@PROJECT_VERSION@") - @PACKAGE_INIT@ -set_and_check(ENTT_INCLUDE_DIRS "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@") - -if(NOT CMAKE_VERSION VERSION_LESS "3.0") - include("${CMAKE_CURRENT_LIST_DIR}/EnTTTargets.cmake") -endif() - +set(EnTT_VERSION "@PROJECT_VERSION@") +include("${CMAKE_CURRENT_LIST_DIR}/EnTTTargets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/modules/entt/cmake/in/cereal.in b/modules/entt/cmake/in/cereal.in deleted file mode 100644 index ef353b7..0000000 --- a/modules/entt/cmake/in/cereal.in +++ /dev/null @@ -1,19 +0,0 @@ -project(cereal-download NONE) -cmake_minimum_required(VERSION 3.2) - -include(ExternalProject) - -ExternalProject_Add( - cereal - GIT_REPOSITORY https://github.com/USCiLab/cereal.git - GIT_TAG v1.2.2 - DOWNLOAD_DIR ${CEREAL_DEPS_DIR} - TMP_DIR ${CEREAL_DEPS_DIR}/tmp - STAMP_DIR ${CEREAL_DEPS_DIR}/stamp - SOURCE_DIR ${CEREAL_DEPS_DIR}/src - BINARY_DIR ${CEREAL_DEPS_DIR}/build - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - TEST_COMMAND "" -) diff --git a/modules/entt/cmake/in/duktape.in b/modules/entt/cmake/in/duktape.in deleted file mode 100644 index 98a00c7..0000000 --- a/modules/entt/cmake/in/duktape.in +++ /dev/null @@ -1,19 +0,0 @@ -project(duktape-download NONE) -cmake_minimum_required(VERSION 3.2) - -include(ExternalProject) - -ExternalProject_Add( - duktape - GIT_REPOSITORY https://github.com/svaarala/duktape-releases.git - GIT_TAG v2.2.0 - DOWNLOAD_DIR ${DUKTAPE_DEPS_DIR} - TMP_DIR ${DUKTAPE_DEPS_DIR}/tmp - STAMP_DIR ${DUKTAPE_DEPS_DIR}/stamp - SOURCE_DIR ${DUKTAPE_DEPS_DIR}/src - BINARY_DIR ${DUKTAPE_DEPS_DIR}/build - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - TEST_COMMAND "" -) diff --git a/modules/entt/cmake/in/entt.pc.in b/modules/entt/cmake/in/entt.pc.in new file mode 100644 index 0000000..d751ed0 --- /dev/null +++ b/modules/entt/cmake/in/entt.pc.in @@ -0,0 +1,8 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ + +Name: EnTT +Description: Gaming meets modern C++ +Url: https://github.com/skypjack/entt +Version: @ENTT_VERSION@ +Cflags: -I${includedir} diff --git a/modules/entt/cmake/in/googletest.in b/modules/entt/cmake/in/googletest.in deleted file mode 100644 index ab1f6e3..0000000 --- a/modules/entt/cmake/in/googletest.in +++ /dev/null @@ -1,19 +0,0 @@ -project(googletest-download NONE) -cmake_minimum_required(VERSION 3.2) - -include(ExternalProject) - -ExternalProject_Add( - googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG master - DOWNLOAD_DIR ${GOOGLETEST_DEPS_DIR} - TMP_DIR ${GOOGLETEST_DEPS_DIR}/tmp - STAMP_DIR ${GOOGLETEST_DEPS_DIR}/stamp - SOURCE_DIR ${GOOGLETEST_DEPS_DIR}/src - BINARY_DIR ${GOOGLETEST_DEPS_DIR}/build - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - TEST_COMMAND "" -) diff --git a/modules/entt/cmake/in/version.h.in b/modules/entt/cmake/in/version.h.in deleted file mode 100644 index f9c9fb5..0000000 --- a/modules/entt/cmake/in/version.h.in +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef ENTT_CONFIG_VERSION_H -#define ENTT_CONFIG_VERSION_H - - -#define ENTT_VERSION "@PROJECT_VERSION@" -#define ENTT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ -#define ENTT_VERSION_MINOR @PROJECT_VERSION_MINOR@ -#define ENTT_VERSION_PATCH @PROJECT_VERSION_PATCH@ - - -#endif // ENTT_CONFIG_VERSION_H diff --git a/modules/entt/conan/build.py b/modules/entt/conan/build.py index 4b3bde8..462f9c5 100644 --- a/modules/entt/conan/build.py +++ b/modules/entt/conan/build.py @@ -4,24 +4,19 @@ from cpt.packager import ConanMultiPackager import os if __name__ == "__main__": + username = os.getenv("GITHUB_ACTOR") + tag_version = os.getenv("GITHUB_REF") + tag_package = os.getenv("GITHUB_REPOSITORY") login_username = os.getenv("CONAN_LOGIN_USERNAME") - username = os.getenv("CONAN_USERNAME") - tag_version = os.getenv("CONAN_PACKAGE_VERSION", os.getenv("TRAVIS_TAG")) - package_version = tag_version.replace("v", "") - package_name_unset = "SET-CONAN_PACKAGE_NAME-OR-CONAN_REFERENCE" - package_name = os.getenv("CONAN_PACKAGE_NAME", package_name_unset) + package_version = tag_version.replace("refs/tags/v", "") + package_name = tag_package.replace("skypjack/", "") reference = "{}/{}".format(package_name, package_version) channel = os.getenv("CONAN_CHANNEL", "stable") upload = os.getenv("CONAN_UPLOAD") stable_branch_pattern = os.getenv("CONAN_STABLE_BRANCH_PATTERN", r"v\d+\.\d+\.\d+.*") test_folder = os.getenv("CPT_TEST_FOLDER", os.path.join("conan", "test_package")) upload_only_when_stable = os.getenv("CONAN_UPLOAD_ONLY_WHEN_STABLE", True) - header_only = os.getenv("CONAN_HEADER_ONLY", False) - pure_c = os.getenv("CONAN_PURE_C", False) - disable_shared = os.getenv("CONAN_DISABLE_SHARED_BUILD", "False") - if disable_shared == "True" and package_name == package_name_unset: - raise Exception("CONAN_DISABLE_SHARED_BUILD: True is only supported when you define CONAN_PACKAGE_NAME") builder = ConanMultiPackager(username=username, reference=reference, @@ -31,10 +26,7 @@ if __name__ == "__main__": stable_branch_pattern=stable_branch_pattern, upload_only_when_stable=upload_only_when_stable, test_folder=test_folder) - if header_only == "False": - builder.add_common_builds(pure_c=pure_c) - else: - builder.add() + builder.add() filtered_builds = [] for settings, options, env_vars, build_requires, reference in builder.items: diff --git a/modules/entt/conan/test_package/test_package.cpp b/modules/entt/conan/test_package/test_package.cpp index e0a14bc..9c99649 100644 --- a/modules/entt/conan/test_package/test_package.cpp +++ b/modules/entt/conan/test_package/test_package.cpp @@ -43,8 +43,8 @@ int main() { for(auto i = 0; i < 10; ++i) { auto entity = registry.create(); - registry.assign(entity, i * 1.f, i * 1.f); - if(i % 2 == 0) { registry.assign(entity, i * .1f, i * .1f); } + registry.emplace(entity, i * 1.f, i * 1.f); + if(i % 2 == 0) { registry.emplace(entity, i * .1f, i * .1f); } } update(dt, registry); diff --git a/modules/entt/conanfile.py b/modules/entt/conanfile.py index 08176d3..c987d77 100644 --- a/modules/entt/conanfile.py +++ b/modules/entt/conanfile.py @@ -19,5 +19,9 @@ class EnttConan(ConanFile): self.copy(pattern="LICENSE", dst="licenses") self.copy(pattern="*", dst="include", src="src", keep_path=True) + def package_info(self): + if not self.in_local_cache: + self.cpp_info.includedirs = ["src"] + def package_id(self): self.info.header_only() diff --git a/modules/entt/deps/.gitignore b/modules/entt/deps/.gitignore deleted file mode 100644 index d6b7ef3..0000000 --- a/modules/entt/deps/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/modules/entt/docs/CMakeLists.txt b/modules/entt/docs/CMakeLists.txt index 8fae056..010c16d 100644 --- a/modules/entt/docs/CMakeLists.txt +++ b/modules/entt/docs/CMakeLists.txt @@ -2,6 +2,7 @@ # Doxygen configuration (documentation) # +set(DOXY_DEPS_DIRECTORY ${EnTT_SOURCE_DIR}/deps) set(DOXY_SOURCE_DIRECTORY ${EnTT_SOURCE_DIR}/src) set(DOXY_DOCS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) set(DOXY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) @@ -13,18 +14,10 @@ add_custom_target( COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxy.cfg WORKING_DIRECTORY ${EnTT_SOURCE_DIR} VERBATIM - SOURCES doxy.in -) - -install( - DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html - DESTINATION share/${PROJECT_NAME}-${PROJECT_VERSION}/ -) - -add_custom_target( - docs_aob SOURCES dox/extra.dox + md/config.md + md/container.md md/core.md md/entity.md md/faq.md @@ -32,7 +25,16 @@ add_custom_target( md/links.md md/locator.md md/meta.md + md/poly.md md/process.md + md/reference.md md/resource.md md/signal.md + md/unreal.md + doxy.in +) + +install( + DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html + DESTINATION share/${PROJECT_NAME}-${PROJECT_VERSION}/ ) diff --git a/modules/entt/docs/doxy.in b/modules/entt/docs/doxy.in index 26badb2..ec5fa94 100644 --- a/modules/entt/docs/doxy.in +++ b/modules/entt/docs/doxy.in @@ -1,4 +1,4 @@ -# Doxyfile 1.8.13 +# Doxyfile 1.9.1 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -17,11 +17,11 @@ # Project related configuration options #--------------------------------------------------------------------------- -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all text -# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv -# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv -# for the list of possible encodings. +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 @@ -93,6 +93,14 @@ ALLOW_UNICODE_NAMES = NO OUTPUT_LANGUAGE = English +# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all generated output in the proper direction. +# Possible values are: None, LTR, RTL and Context. +# The default value is: None. + +OUTPUT_TEXT_DIRECTION = None + # If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. @@ -179,6 +187,16 @@ SHORT_NAMES = NO JAVADOC_AUTOBRIEF = NO +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus @@ -199,6 +217,14 @@ QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. @@ -226,16 +252,15 @@ TAB_SIZE = 4 # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines. +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) ALIASES = -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all @@ -264,28 +289,40 @@ OPTIMIZE_FOR_FORTRAN = NO OPTIMIZE_OUTPUT_VHDL = NO +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, -# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: -# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: -# Fortran. In the later case the parser tries to guess whether the code is fixed -# or free formatted code, this is the default for Fortran type files), VHDL. For -# instance to make doxygen treat .inc files as Fortran files (default is PHP), -# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable -# documentation. See http://daringfireball.net/projects/markdown/ for details. +# documentation. See https://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. @@ -297,10 +334,10 @@ MARKDOWN_SUPPORT = YES # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. -# Minimum value: 0, maximum value: 99, default value: 0. +# Minimum value: 0, maximum value: 99, default value: 5. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. -TOC_INCLUDE_HEADINGS = 4 +TOC_INCLUDE_HEADINGS = 5 # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can @@ -327,7 +364,7 @@ BUILTIN_STL_SUPPORT = NO CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. @@ -413,6 +450,19 @@ TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which efficively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -433,6 +483,12 @@ EXTRACT_ALL = NO EXTRACT_PRIVATE = NO +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. @@ -470,6 +526,13 @@ EXTRACT_LOCAL_METHODS = NO EXTRACT_ANON_NSPACES = NO +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation @@ -487,8 +550,8 @@ HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# (class|struct|union) declarations. If set to NO, these declarations will be -# included in the documentation. +# declarations. If set to NO, these declarations will be included in the +# documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO @@ -507,11 +570,18 @@ HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES, upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. # The default value is: system dependent. CASE_SENSE_NAMES = YES @@ -698,7 +768,7 @@ LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. @@ -743,13 +813,17 @@ WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. # The default value is: NO. WARN_NO_PARAMDOC = YES # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. # The default value is: NO. WARN_AS_ERROR = NO @@ -781,14 +855,14 @@ WARN_LOGFILE = # Note: If this tag is empty the current directory is searched. INPUT = @DOXY_SOURCE_DIRECTORY@ \ - @DOXY_DOCS_DIRECTORY@ \ - @PROJECT_SOURCE_DIR@/README.md + @DOXY_DOCS_DIRECTORY@ \ + @PROJECT_SOURCE_DIR@/README.md # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: http://www.gnu.org/software/libiconv) for the list of -# possible encodings. +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 @@ -801,11 +875,15 @@ INPUT_ENCODING = UTF-8 # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by doxygen. # +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, -# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. +# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), +# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, +# *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.h \ *.hpp \ @@ -825,7 +903,7 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = +EXCLUDE = @DOXY_DEPS_DIRECTORY@ # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -963,7 +1041,7 @@ INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# function all documented functions referencing it will be listed. +# entity all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO @@ -995,12 +1073,12 @@ SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system -# (see http://www.gnu.org/software/global/global.html). You will need version +# (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # @@ -1023,16 +1101,22 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES # If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the -# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the -# cost of reduced performance. This can be particularly helpful with template -# rich C++ code for which doxygen's built-in parser lacks the necessary type -# information. +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which doxygen's built-in parser lacks the necessary type information. # Note: The availability of this option depends on whether or not doxygen was -# generated with the -Duse-libclang=ON option for CMake. +# generated with the -Duse_libclang=ON option for CMake. # The default value is: NO. CLANG_ASSISTED_PARSING = NO +# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to +# YES then doxygen will add the directory of each input to the include path. +# The default value is: YES. + +CLANG_ADD_INC_PATHS = YES + # If clang assisted parsing is enabled you can provide the compiler with command # line options that you would normally use when invoking the compiler. Note that # the include paths will already be set by doxygen for the files and directories @@ -1041,6 +1125,19 @@ CLANG_ASSISTED_PARSING = NO CLANG_OPTIONS = +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- @@ -1052,13 +1149,6 @@ CLANG_OPTIONS = ALPHABETICAL_INDEX = YES -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored @@ -1159,7 +1249,7 @@ HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see -# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. @@ -1195,6 +1285,17 @@ HTML_COLORSTYLE_GAMMA = 80 HTML_TIMESTAMP = NO +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. @@ -1218,13 +1319,14 @@ HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: http://developer.apple.com/tools/xcode/), introduced with -# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html -# for more information. +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1263,8 +1365,8 @@ DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. +# (see: +# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML @@ -1294,7 +1396,7 @@ CHM_FILE = HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the master .chm file (NO). +# (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. @@ -1339,7 +1441,8 @@ QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace -# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1347,8 +1450,8 @@ QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- -# folders). +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1356,30 +1459,30 @@ QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: -# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = @@ -1456,6 +1559,17 @@ TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML @@ -1465,7 +1579,7 @@ EXT_LINKS_IN_WINDOW = NO FORMULA_FONTSIZE = 10 -# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # @@ -1476,8 +1590,14 @@ FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# http://www.mathjax.org) which uses client side Javascript for the rendering +# https://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path @@ -1489,7 +1609,7 @@ USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. @@ -1504,11 +1624,11 @@ MATHJAX_FORMAT = HTML-CSS # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of -# MathJax from http://www.mathjax.org before deployment. -# The default value is: http://cdn.mathjax.org/mathjax/latest. +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. # This tag requires that the tag USE_MATHJAX is set to YES. -MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest +MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/ # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example @@ -1519,7 +1639,8 @@ MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. @@ -1547,7 +1668,7 @@ MATHJAX_CODEFILE = SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a web server instead of a web client using Javascript. There +# implemented using a web server instead of a web client using JavaScript. There # are two flavors of web server based searching depending on the EXTERNAL_SEARCH # setting. When disabled, doxygen will generate a PHP script for searching and # an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing @@ -1566,7 +1687,8 @@ SERVER_BASED_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: http://xapian.org/). +# Xapian (see: +# https://xapian.org/). # # See the section "External Indexing and Searching" for details. # The default value is: NO. @@ -1579,8 +1701,9 @@ EXTERNAL_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: http://xapian.org/). See the section "External Indexing and -# Searching" for details. +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. # This tag requires that the tag SEARCHENGINE is set to YES. SEARCHENGINE_URL = @@ -1631,21 +1754,35 @@ LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. # -# Note that when enabling USE_PDFLATEX this option is only used for generating -# bitmaps for formulas in the HTML output, but not in the Makefile that is -# written to the output directory. -# The default file is: latex. +# Note that when not enabling USE_PDFLATEX the default is latex when enabling +# USE_PDFLATEX the default is pdflatex and when in the later case latex is +# chosen this is overwritten by pdflatex. For specific output languages the +# default can have been set differently, this depends on the implementation of +# the output language. # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_CMD_NAME = latex +LATEX_CMD_NAME = # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate # index for LaTeX. +# Note: This tag is used in the Makefile / make.bat. +# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file +# (.tex). # The default file is: makeindex. # This tag requires that the tag GENERATE_LATEX is set to YES. MAKEINDEX_CMD_NAME = makeindex +# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to +# generate index for LaTeX. In case there is no backslash (\) as first character +# it will be automatically added in the LaTeX code. +# Note: This tag is used in the generated output file (.tex). +# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. +# The default value is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_MAKEINDEX_CMD = makeindex + # If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX # documents. This may be useful for small projects and may help to save some # trees in general. @@ -1730,9 +1867,11 @@ LATEX_EXTRA_FILES = PDF_HYPERLINKS = YES -# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate -# the PDF file directly from the LaTeX files. Set this option to YES, to get a -# higher quality PDF documentation. +# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. # The default value is: YES. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -1766,7 +1905,7 @@ LATEX_SOURCE_CODE = NO # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. See -# http://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. # The default value is: plain. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -1780,6 +1919,14 @@ LATEX_BIB_STYLE = plain LATEX_TIMESTAMP = NO +# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) +# path from which the emoji images will be read. If a relative path is entered, +# it will be relative to the LATEX_OUTPUT directory. If left blank the +# LATEX_OUTPUT directory will be used. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EMOJI_DIRECTORY = + #--------------------------------------------------------------------------- # Configuration options related to the RTF output #--------------------------------------------------------------------------- @@ -1819,9 +1966,9 @@ COMPACT_RTF = NO RTF_HYPERLINKS = NO -# Load stylesheet definitions from file. Syntax is similar to doxygen's config -# file, i.e. a series of assignments. You only have to provide replacements, -# missing definitions are set to their default value. +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# configuration file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. # # See also section "Doxygen usage" for information on how to generate the # default style sheet that doxygen normally uses. @@ -1830,8 +1977,8 @@ RTF_HYPERLINKS = NO RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an RTF document. Syntax is -# similar to doxygen's config file. A template extensions file can be generated -# using doxygen -e rtf extensionFile. +# similar to doxygen's configuration file. A template extensions file can be +# generated using doxygen -e rtf extensionFile. # This tag requires that the tag GENERATE_RTF is set to YES. RTF_EXTENSIONS_FILE = @@ -1917,6 +2064,13 @@ XML_OUTPUT = xml XML_PROGRAMLISTING = YES +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# namespace members in file scope as well, matching the HTML output. +# The default value is: NO. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_NS_MEMB_FILE_SCOPE = NO + #--------------------------------------------------------------------------- # Configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- @@ -1949,9 +2103,9 @@ DOCBOOK_PROGRAMLISTING = NO #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sf.net) file that captures the -# structure of the code including all documentation. Note that this feature is -# still experimental and incomplete at the moment. +# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# the structure of the code including all documentation. Note that this feature +# is still experimental and incomplete at the moment. # The default value is: NO. GENERATE_AUTOGEN_DEF = NO @@ -2011,7 +2165,7 @@ ENABLE_PREPROCESSING = YES # The default value is: NO. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -MACRO_EXPANSION = NO +MACRO_EXPANSION = YES # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then # the macro expansion is limited to the macros specified with the PREDEFINED and @@ -2118,12 +2272,6 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of 'which perl'). -# The default file (with absolute path) is: /usr/bin/perl. - -PERL_PATH = /usr/bin/perl - #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- @@ -2137,15 +2285,6 @@ PERL_PATH = /usr/bin/perl CLASS_DIAGRAMS = YES -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see: -# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the -# documentation. The MSCGEN_PATH tag allows you to specify the directory where -# the mscgen tool resides. If left empty the tool is assumed to be found in the -# default search path. - -MSCGEN_PATH = - # You can include diagrams made with dia in doxygen documentation. Doxygen will # then run dia to produce the diagram and insert it in the documentation. The # DIA_PATH tag allows you to specify the directory where the dia binary resides. @@ -2243,10 +2382,32 @@ UML_LOOK = NO # but if the number exceeds 15, the total amount of fields shown is limited to # 10. # Minimum value: 0, maximum value: 100, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. +# This tag requires that the tag UML_LOOK is set to YES. UML_LIMIT_NUM_FIELDS = 10 +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will wrapped across multiple lines. Some heuristics are apply +# to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + # If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and # collaboration graphs will show the relations between templates and their # instances. @@ -2438,9 +2599,11 @@ DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate # files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc and +# plantuml temporary files. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. DOT_CLEANUP = YES diff --git a/modules/entt/docs/md/config.md b/modules/entt/docs/md/config.md new file mode 100644 index 0000000..2c6e84e --- /dev/null +++ b/modules/entt/docs/md/config.md @@ -0,0 +1,111 @@ +# Crash Course: configuration + + +# Table of Contents + +* [Introduction](#introduction) +* [Definitions](#definitions) + * [ENTT_NOEXCEPTION](#entt_noexcept) + * [ENTT_USE_ATOMIC](#entt_use_atomic) + * [ENTT_ID_TYPE](#entt_id_type) + * [ENTT_SPARSE_PAGE](#entt_sparse_page) + * [ENTT_PACKED_PAGE](#entt_packed_page) + * [ENTT_ASSERT](#entt_assert) + * [ENTT_DISABLE_ASSERT](#entt_disable_assert) + * [ENTT_NO_ETO](#entt_no_eto) + * [ENTT_STANDARD_CPP](#entt_standard_cpp) + + + +# Introduction + +`EnTT` doesn't offer many hooks for customization but it certainly offers +some.
+In the vast majority of cases, users will have no interest in changing the +default parameters. For all other cases, the list of possible configurations +with which it's possible to adjust the behavior of the library at runtime can be +found below. + +# Definitions + +All options are intended as parameters to the compiler (or user-defined macros +within the compilation units, if preferred).
+Each parameter can result in internal library definitions. It's not recommended +to try to also modify these definitions, since there is no guarantee that they +will remain stable over time unlike the options below. + +## ENTT_NOEXCEPTION + +This parameter can be used to switch off exception handling in `EnTT`.
+To do this, simply define the variable without assigning any value to it. This +is roughly equivalent to setting the compiler flag `-ff-noexceptions`. + +## ENTT_USE_ATOMIC + +In general, `EnTT` doesn't offer primitives to support multi-threading. Many of +the features can be split over multiple threads without any explicit control and +the user is the only one who knows if and when a synchronization point is +required.
+However, some features aren't easily accessible to users and can be made +thread-safe by means of this definition. + +## ENTT_ID_TYPE + +`entt::id_type` is directly controlled by this definition and widely used within +the library.
+By default, its type is `std::uint32_t`. However, users can define a different +default type if necessary. + +## ENTT_SPARSE_PAGE + +It's known that the ECS module of `EnTT` is based on _sparse sets_. What is less +known perhaps is that the sparse arrays are paged to reduce memory usage.
+Default size of pages (that is, the number of elements they contain) is 4096 but +users can adjust it if appropriate. In all case, the chosen value **must** be a +power of 2. + +## ENTT_PACKED_PAGE + +Similar to sparse arrays, packed arrays of components are paginated as well. In +However, int this case the aim isn't to reduce memory usage but to have pointer +stability upon component creation.
+Default size of pages (that is, the number of elements they contain) is 1024 but +users can adjust it if appropriate. In all case, the chosen value **must** be a +power of 2. + +## ENTT_ASSERT + +For performance reasons, `EnTT` doesn't use exceptions or any other control +structures. In fact, it offers many features that result in undefined behavior +if not used correctly.
+To get around this, the library relies on a lot of asserts for the purpose of +detecting errors in debug builds. By default, it uses `assert` internally, but +users are allowed to overwrite its behavior by setting this variable. + +### ENTT_DISABLE_ASSERT + +Assertions may in turn affect performance to an extent when enabled. Whether +`ENTT_ASSERT` is redefined or not, all asserts can be disabled at once by means +of this definition.
+Note that `ENTT_DISABLE_ASSERT` takes precedence over the redefinition of +`ENTT_ASSERT` and is therefore meant to disable all controls no matter what. + +## ENTT_NO_ETO + +In order to reduce memory consumption and increase performance, empty types are +never stored by the ECS module of `EnTT`.
+Use this variable to treat these types like all others and therefore to create a +dedicated storage for them. + +## ENTT_STANDARD_CPP + +`EnTT` mixes non-standard language features with others that are perfectly +compliant to offer some of its functionalities.
+This definition will prevent the library from using non-standard techniques, +that is, functionalities that aren't fully compliant with the standard C++.
+While there are no known portability issues at the time of this writing, this +should make the library fully portable anyway if needed. diff --git a/modules/entt/docs/md/container.md b/modules/entt/docs/md/container.md new file mode 100644 index 0000000..173e729 --- /dev/null +++ b/modules/entt/docs/md/container.md @@ -0,0 +1,67 @@ +# Crash Course: containers + + +# Table of Contents + +* [Introduction](#introduction) +* [Containers](#containers) + * [Dense map](#dense-map) + * [Dense set](#dense-set) + + + +# Introduction + +The standard C++ library offers a wide range of containers and it's really +difficult to do better (although it's very easy to do worse, as many examples +available online demonstrate).
+`EnTT` doesn't try in any way to replace what is offered by the standard. Quite +the opposite, given the widespread use that is made of standard containers.
+However, the library also tries to fill a gap in features and functionality by +making available some containers initially developed for internal use. + +This section of the library is likely to grow larger over time. However, for the +moment it's quite small and mainly aimed at satisfying some internal needs.
+For all containers made available, full test coverage and stability over time is +guaranteed as usual. + +# Containers + +## Dense map + +The dense map made available in `EnTT` is a hash map that aims to return a +packed array of elements, so as to reduce the number of jumps in memory during +iterations.
+The implementation is based on _sparse sets_ and each bucket is identified by an +implicit list within the packed array itself. + +The interface is very close to its counterpart in the standard library, that is, +`std::unordered_map`.
+However, both local and non-local iterators returned by a dense map belong to +the input iterator category although they respectively model the concepts of a +_forward iterator_ type and a _random access iterator_ type.
+This is because they return a pair of references rather than a reference to a +pair. In other words, dense maps return a so called _proxy iterator_ the value +type of which is: + +* `std::pair` for non-const iterator types. +* `std::pair` for const iterator types. + +This is quite different from what any standard library map returns and should be +taken into account when looking for a drop-in replacement. + +## Dense set + +The dense set made available in `EnTT` is a hash set that aims to return a +packed array of elements, so as to reduce the number of jumps in memory during +iterations.
+The implementation is based on _sparse sets_ and each bucket is identified by an +implicit list within the packed array itself. + +The interface is in all respects similar to its counterpart in the standard +library, that is, `std::unordered_set`.
+Therefore, there is no need to go into the API description. diff --git a/modules/entt/docs/md/core.md b/modules/entt/docs/md/core.md index f795dd9..3536a25 100644 --- a/modules/entt/docs/md/core.md +++ b/modules/entt/docs/md/core.md @@ -6,11 +6,34 @@ # Table of Contents * [Introduction](#introduction) -* [Compile-time identifiers](#compile-time-identifiers) -* [Runtime identifiers](#runtime-identifiers) +* [Any as in any type](#any-as-in-any-type) + * [Small buffer optimization](#small-buffer-optimization) + * [Alignment requirement](#alignment-requirement) +* [Compressed pair](#compressed-pair) +* [Enum as bitmask](#enum-as-bitmask) * [Hashed strings](#hashed-strings) + * [Wide characters](wide-characters) * [Conflicts](#conflicts) +* [Memory](#memory) + * [Power of two and fast modulus](#power-of-two-and-fast-modulus) + * [Allocator aware unique pointers](#allocator-aware-unique-pointers) * [Monostate](#monostate) +* [Type support](#type-support) + * [Built-in RTTI support](#built-in-rtti-support) + * [Type info](#type-info) + * [Almost unique identifiers](#almost-unique-identifiers) + * [Type traits](#type-traits) + * [Size of](#size-of) + * [Is applicable](#is-applicable) + * [Constness as](#constness-as) + * [Member class type](#member-class-type) + * [Integral constant](#integral-constant) + * [Tag](#tag) + * [Type list and value list](#type-list-and-value-list) +* [Unique sequential identifiers](#unique-sequential-identifiers) + * [Compile-time generator](#compile-time-generator) + * [Runtime generator](#runtime-generator) +* [Utilities](#utilities) @@ -22,84 +45,249 @@ of the library itself.
Hardly users will include these features in their code, but it's worth describing what `EnTT` offers so as not to reinvent the wheel in case of need. -# Compile-time identifiers +# Any as in any type -Sometimes it's useful to be able to give unique identifiers to types at -compile-time.
-There are plenty of different solutions out there and I could have used one of -them. However, I decided to spend my time to define a compact and versatile tool -that fully embraces what the modern C++ has to offer. +`EnTT` comes with its own `any` type. It may seem redundant considering that +C++17 introduced `std::any`, but it is not (hopefully).
+First of all, the _type_ returned by an `std::any` is a const reference to an +`std::type_info`, an implementation defined class that's not something everyone +wants to see in a software. Furthermore, there is no way to connect it with the +type system of the library and therefore with its integrated RTTI support.
+Note that this class is largely used internally by the library itself. + +The API is very similar to that of its most famous counterpart, mainly because +this class serves the same purpose of being an opaque container for any type of +value.
+Instances of `any` also minimize the number of allocations by relying on a well +known technique called _small buffer optimization_ and a fake vtable. -The _result of my efforts_ is the `identifier` class template: +Creating an object of the `any` type, whether empty or not, is trivial: ```cpp -#include +// an empty container +entt::any empty{}; -// defines the identifiers for the given types -using id = entt::identifier; +// a container for an int +entt::any any{0}; -// ... +// in place construction +entt::any in_place{std::in_place_type, 42}; +``` -switch(a_type_identifier) { -case id::type: - // ... - break; -case id::type: - // ... - break; -default: - // ... -} +Alternatively, the `make_any` function serves the same purpose but requires to +always be explicit about the type: + +```cpp +entt::any any = entt::make_any(42); ``` -This is all what the class template has to offer: a `type` inline variable that -contains a numerical identifier for the given type. It can be used in any -context where constant expressions are required. +In both cases, the `any` class takes the burden of destroying the contained +element when required, regardless of the storage strategy used for the specific +object.
+Furthermore, an instance of `any` isn't tied to an actual type. Therefore, the +wrapper is reconfigured when it's assigned a new object of a type other than +the one it contains. -As long as the list remains unchanged, identifiers are also guaranteed to be the -same for every run. In case they have been used in a production environment and -a type has to be removed, one can just use a placeholder to left the other -identifiers unchanged: +There exists also a way to directly assign a value to the variable contained by +an `entt::any`, without necessarily replacing it. This is especially useful when +the object is used in _aliasing mode_, as described below: ```cpp -template struct ignore_type {}; +entt::any any{42}; +entt::any value{3}; -using id = entt::identifier< - a_type_still_valid, - ignore_type, - another_type_still_valid ->; +// assigns by copy +any.assign(value); + +// assigns by move +any.assign(std::move(value)); ``` -A bit ugly to see, but it works at least. +The `any` class will also perform a check on the type information and whether or +not the original type was copy or move assignable, as appropriate.
+In all cases, the `assign` function returns a boolean value to indicate the +success or failure of the operation. -# Runtime identifiers +When in doubt about the type of object contained, the `type` member function of +`any` returns a const reference to the `type_info` associated with its element, +or `type_id()` if the container is empty. The type is also used internally +when comparing two `any` objects: -Sometimes it's useful to be able to give unique identifiers to types at -runtime.
-There are plenty of different solutions out there and I could have used one of -them. In fact, I adapted the most common one to my requirements and used it -extensively within the entire library. +```cpp +if(any == empty) { /* ... */ } +``` -It's the `family` class. Here is an example of use directly from the -entity-component system: +In this case, before proceeding with a comparison, it's verified that the _type_ +of the two objects is actually the same.
+Refer to the `EnTT` type system documentation for more details about how +`type_info` works and on possible risks of a comparison. + +A particularly interesting feature of this class is that it can also be used as +an opaque container for const and non-const references: ```cpp -using component_family = entt::family; +int value = 42; -// ... +entt::any any{std::in_place_type(value)}; +entt::any cany = entt::make_any(value); +entt::any fwd = entt::forward_as_any(value); -template -component_type component() const noexcept { - return component_family::type; -} +any.emplace(value); ``` -This is all what a _family_ has to offer: a `type` inline variable that contains -a numerical identifier for the given type. +In other words, whenever `any` is explicitly told to construct an _alias_, it +acts as a pointer to the original instance rather than making a copy of it or +moving it internally. The contained object is never destroyed and users must +ensure that its lifetime exceeds that of the container.
+Similarly, it's possible to create non-owning copies of `any` from an existing +object: + +```cpp +// aliasing constructor +entt::any ref = other.as_ref(); +``` + +In this case, it doesn't matter if the original container actually holds an +object or acts already as a reference for unmanaged elements, the new instance +thus created won't create copies and will only serve as a reference for the +original item.
+This means that, starting from the example above, both `ref` and `other` will +point to the same object, whether it's initially contained in `other` or already +an unmanaged element. + +As a side note, it's worth mentioning that, while everything works transparently +when it comes to non-const references, there are some exceptions when it comes +to const references.
+In particular, the `data` member function invoked on a non-const instance of +`any` that wraps a const reference will return a null pointer in all cases. + +To cast an instance of `any` to a type, the library offers a set of `any_cast` +functions in all respects similar to their most famous counterparts.
+The only difference is that, in the case of `EnTT`, these won't raise exceptions +but will only trigger an assert in debug mode, otherwise resulting in undefined +behavior in case of misuse in release mode. + +## Small buffer optimization + +The `any` class uses a technique called _small buffer optimization_ to reduce +the number of allocations where possible.
+The default reserved size for an instance of `any` is `sizeof(double[2])`. +However, this is also configurable if needed. In fact, `any` is defined as an +alias for `basic_any`, where `Len` is the size above.
+Users can easily set a custom size or define their own aliases: + +```cpp +using my_any = entt::basic_any; +``` + +This feature, in addition to allowing the choice of a size that best suits the +needs of an application, also offers the possibility of forcing dynamic creation +of objects during construction.
+In other terms, if the size is 0, `any` avoids the use of any optimization and +always dynamically allocates objects (except for aliasing cases). + +Note that the size of the internal storage as well as the alignment requirements +are directly part of the type and therefore contribute to define different types +that won't be able to interoperate with each other. + +## Alignment requirement + +The alignment requirement is optional and by default the most stringent (the +largest) for any object whose size is at most equal to the one provided.
+The `basic_any` class template inspects the alignment requirements in each case, +even when not provided and may decide not to use the small buffer optimization +in order to meet them. + +The alignment requirement is provided as an optional second parameter following +the desired size for the internal storage: + +```cpp +using my_any = entt::basic_any; +``` + +Note that the alignment requirements as well as the size of the internal storage +are directly part of the type and therefore contribute to define different types +that won't be able to interoperate with each other. + +# Compressed pair + +Primarily designed for internal use and far from being feature complete, the +`compressed_pair` class does exactly what it promises: it tries to reduce the +size of a pair by exploiting _Empty Base Class Optimization_ (or _EBCO_).
+This class **is not** a drop-in replacement for `std::pair`. However, it offers +enough functionalities to be a good alternative for when reducing memory usage +is more important than having some cool and probably useless feature. + +Although the API is very close to that of `std::pair` (apart from the fact that +the template parameters are inferred from the constructor and therefore there is +no` entt::make_compressed_pair`), the major difference is that `first` and +`second` are functions for implementation needs: + +```cpp +entt::compressed_pair pair{0, 3.}; +pair.first() = 42; +``` + +There isn't much to describe then. It's recommended to rely on documentation and +intuition. At the end of the day, it's just a pair and nothing more. + +# Enum as bitmask + +Sometimes it's useful to be able to use enums as bitmasks. However, enum classes +aren't really suitable for the purpose out of the box. Main problem is that they +don't convert implicitly to their underlying type.
+All that remains is to make a choice between using old-fashioned enums (with all +their problems that I don't want to discuss here) or writing _ugly_ code. + +Fortunately, there is also a third way: adding enough operators in the global +scope to treat enum classes as bitmask transparently.
+The ultimate goal is to be able to write code like the following (or maybe +something more meaningful, but this should give a grasp and remain simple at the +same time): + +```cpp +enum class my_flag { + unknown = 0x01, + enabled = 0x02, + disabled = 0x04 +}; + +const my_flag flags = my_flag::enabled; +const bool is_enabled = !!(flags & my_flag::enabled); +``` -Please, note that identifiers aren't guaranteed to be the same for every run. -Indeed it mostly depends on the flow of execution. +The problem with adding all operators to the global scope is that these will +come into play even when not required, with the risk of introducing errors that +are difficult to deal with.
+However, C++ offers enough tools to get around this problem. In particular, the +library requires users to register all enum classes for which bitmask support +should be enabled: + +```cpp +template<> +struct entt::enum_as_bitmask + : std::true_type +{}; +``` + +This is handy when dealing with enum classes defined by third party libraries +and over which the users have no control. However, it's also verbose and can be +avoided by adding a specific value to the enum class itself: + +```cpp +enum class my_flag { + unknown = 0x01, + enabled = 0x02, + disabled = 0x04, + _entt_enum_as_bitmask +}; +``` + +In this case, there is no need to specialize the `enum_as_bitmask` traits, since +`EnTT` will automatically detect the flag and enable the bitmask support.
+Once the enum class has been registered (in one way or the other) all the most +common operators will be available, such as `&`, `|` but also `&=` and `|=`. +Refer to the official documentation for the full list of operators. # Hashed strings @@ -108,7 +296,8 @@ human-readable identifiers in the codebase while using their numeric counterparts at runtime, thus without affecting performance.
The class has an implicit `constexpr` constructor that chews a bunch of characters. Once created, all what one can do with it is getting back the -original string or converting it into a number.
+original string through the `data` member function or converting the instance +into a number.
The good part is that a hashed string can be used wherever a constant expression is required and no _string-to-number_ conversion will take place at runtime if used carefully. @@ -127,9 +316,47 @@ There is also a _user defined literal_ dedicated to hashed strings to make them more user-friendly: ```cpp +using namespace entt::literals; constexpr auto str = "text"_hs; ``` +To use it, remember that all user defined literals in `EnTT` are enclosed in the +`entt::literals` namespace. Therefore, the entire namespace or selectively the +literal of interest must be explicitly included before each use, a bit like +`std::literals`.
+Finally, in case users need to create hashed strings at runtime, this class also +offers the necessary functionalities: + +```cpp +std::string orig{"text"}; + +// create a full-featured hashed string... +entt::hashed_string str{orig.c_str()}; + +// ... or compute only the unique identifier +const auto hash = entt::hashed_string::value(orig.c_str()); +``` + +This possibility shouldn't be exploited in tight loops, since the computation +takes place at runtime and no longer at compile-time and could therefore impact +performance to some degrees. + +## Wide characters + +The hashed string has a design that is close to that of an `std::basic_string`. +It means that `hashed_string` is nothing more than an alias for +`basic_hashed_string`. For those who want to use the C++ type for wide +character representation, there exists also the alias `hashed_wstring` for +`basic_hashed_string`.
+In this case, the user defined literal to use to create hashed strings on the +fly is `_hws`: + +```cpp +constexpr auto str = L"text"_hws; +``` + +Note that the hash type of the `hashed_wstring` is the same of its counterpart. + ## Conflicts The hashed string class uses internally FNV-1a to compute the numeric @@ -143,6 +370,54 @@ and over which users have not the control. Choosing a slightly different identifier is probably the best solution to make the conflict disappear in this case. +# Memory + +There are a handful of tools within EnTT to interact with memory in one way or +another.
+Some are geared towards simplifying the implementation of (internal or external) +allocator aware containers. Others, on the other hand, are designed to help the +developer with everyday problems. + +The former are very specific and for niche problems. These are tools designed to +unwrap fancy or plain pointers (`to_address`) or to help forget the meaning of +acronyms like _POCCA_, _POCMA_ or _POCS_.
+I won't describe them here in detail. Instead, I recommend reading the inline +documentation to those interested in the subject. + +## Power of two and fast modulus + +Finding out if a number is a power of two (`is_power_of_two`) or what the next +power of two is given a random value (`next_power_of_two`) is very useful at +times.
+For example, it helps to allocate memory in pages having a size suitable for the +fast modulus: + +```cpp +const std::size_t result = entt::fast_mod(value, modulus); +``` + +Where `modulus` is necessarily a power of two. Perhaps not everyone knows that +this type of operation is far superior in terms of performance to the basic +modulus and for this reason preferred in many areas. + +## Allocator aware unique pointers + +A nasty thing in C++ (at least up to C++20) is the fact that shared pointers +support allocators while unique pointers don't.
+There is a proposal at the moment that also shows among the other things how +this can be implemented without any compiler support. + +The `allocate_unique` function follows this proposal, making a virtue out of +necessity: + +```cpp +std::unique_ptr> ptr = entt::allocate_unique(allocator, arguments); +``` + +Although the internal implementation is slightly different from what is proposed +for the standard, this function offers an API that is a drop-in replacement for +the same feature. + # Monostate The monostate pattern is often presented as an alternative to a singleton based @@ -165,3 +440,481 @@ entt::monostate<"mykey"_hs>{} = 42; const bool b = entt::monostate<"mykey"_hs>{}; const int i = entt::monostate{}; ``` + +# Type support + +`EnTT` provides some basic information about types of all kinds.
+It also offers additional features that are not yet available in the standard +library or that will never be. + +## Built-in RTTI support + +Runtime type identification support (or RTTI) is one of the most frequently +disabled features in the C++ world, especially in the gaming sector. Regardless +of the reasons for this, it's often a shame not to be able to rely on opaque +type information at runtime.
+The library tries to fill this gap by offering a built-in system that doesn't +serve as a replacement but comes very close to being one and offers similar +information to that provided by its counterpart. + +Basically, the whole system relies on a handful of classes. In particular: + +* The unique sequential identifier associated with a given type: + + ```cpp + auto index = entt::type_index::value(); + ``` + + The returned value isn't guaranteed to be stable across different runs. + However, it can be very useful as index in associative and unordered + associative containers or for positional accesses in a vector or an array. + + So as not to conflict with the other tools available, the `family` class isn't + used to generate these indexes. Therefore, the numeric identifiers returned by + the two tools may differ.
+ On the other hand, this leaves users with full powers over the `family` class + and therefore the generation of custom runtime sequences of indices for their + own purposes, if necessary. + + An external generator can also be used if needed. In fact, `type_index` can be + specialized by type and is also _sfinae-friendly_ in order to allow more + refined specializations such as: + + ```cpp + template + struct entt::type_index> { + static entt::id_type value() ENTT_NOEXCEPT { + return Type::index(); + } + }; + ``` + + Note that indexes **must** still be generated sequentially in this case.
+ The tool is widely used within `EnTT`. Generating indices not sequentially + would break an assumption and would likely lead to undesired behaviors. + +* The hash value associated with a given type: + + ```cpp + auto hash = entt::type_hash::value(); + ``` + + In general, the `value` function exposed by `type_hash` is also `constexpr` + but this isn't guaranteed for all compilers and platforms (although it's valid + with the most well-known and popular ones). + + This function **can** use non-standard features of the language for its own + purposes. This makes it possible to provide compile-time identifiers that + remain stable across different runs.
+ In all cases, users can prevent the library from using these features by means + of the `ENTT_STANDARD_CPP` definition. In this case, there is no guarantee + that identifiers remain stable across executions. Moreover, they are generated + at runtime and are no longer a compile-time thing. + + As for `type_index`, also `type_hash` is a _sfinae-friendly_ class that can be + specialized in order to customize its behavior globally or on a per-type or + per-traits basis. + +* The name associated with a given type: + + ```cpp + auto name = entt::type_name::value(); + ``` + + The name associated with a type is extracted from some information generally + made available by the compiler in use. Therefore, it may differ depending on + the compiler and may be empty in the event that this information isn't + available.
+ For example, given the following class: + + ```cpp + struct my_type { /* ... */ }; + ``` + + The name is `my_type` when compiled with GCC or CLang and `struct my_type` + when MSVC is in use.
+ Most of the time the name is also retrieved at compile-time and is therefore + always returned through an `std::string_view`. Users can easily access it and + modify it as needed, for example by removing the word `struct` to standardize + the result. `EnTT` won't do this for obvious reasons, since it requires + copying and creating a new string potentially at runtime. + + This function **can** use non-standard features of the language for its own + purposes. Users can prevent the library from using non-standard features by + means of the `ENTT_STANDARD_CPP` definition. In this case, the name will be + empty by default. + + As for `type_index`, also `type_name` is a _sfinae-friendly_ class that can be + specialized in order to customize its behavior globally or on a per-type or + per-traits basis. + +These are then combined into utilities that aim to offer an API that is somewhat +similar to that offered by the language. + +### Type info + +The `type_info` class isn't a drop-in replacement for `std::type_info` but can +provide similar information which are not implementation defined and don't +require to enable RTTI.
+Therefore, they can sometimes be even more reliable than those obtained +otherwise. + +Its type defines an opaque class that is also copyable and movable.
+Objects of this type are generally returned by the `type_id` functions: + +```cpp +// by type +auto info = entt::type_id(); + +// by value +auto other = entt::type_id(42); +``` + +All elements thus received are nothing more than const references to instances +of `type_info` with static storage duration.
+This is convenient for saving the entire object aside for the cost of a pointer. +However, nothing prevents from constructing `type_info` objects directly: + +```cpp +entt::type_info info{std::in_place_type}; +``` + +These are the information made available by `type_info`: + +* The index associated with a given type: + + ```cpp + auto idx = entt::type_id().index(); + ``` + + This is also an alias for the following: + + ```cpp + auto idx = entt::type_index>>::value(); + ``` + +* The hash value associated with a given type: + + ```cpp + auto hash = entt::type_id().hash(); + ``` + + This is also an alias for the following: + + ```cpp + auto hash = entt::type_hash>>::value(); + ``` + +* The name associated with a given type: + + ```cpp + auto name = entt::type_id().name(); + ``` + + This is also an alias for the following: + + ```cpp + auto name = entt::type_name>>::value(); + ``` + +Where all accessed features are available at compile-time, the `type_info` class +is also fully `constexpr`. However, this cannot be guaranteed in advance and +depends mainly on the compiler in use and any specializations of the classes +described above. + +### Almost unique identifiers + +Since the default non-standard, compile-time implementation of `type_hash` makes +use of hashed strings, it may happen that two types are assigned the same hash +value.
+In fact, although this is quite rare, it's not entirely excluded. + +Another case where two types are assigned the same identifier is when classes +from different contexts (for example two or more libraries loaded at runtime) +have the same fully qualified name. In this case, also `type_name` will return +the same value for the two types.
+Fortunately, there are several easy ways to deal with this: + +* The most trivial one is to define the `ENTT_STANDARD_CPP` macro. Runtime + identifiers don't suffer from the same problem in fact. However, this solution + doesn't work well with a plugin system, where the libraries aren't linked. + +* Another possibility is to specialize the `type_name` class for one of the + conflicting types, in order to assign it a custom identifier. This is probably + the easiest solution that also preserves the feature of the tool. + +* A fully customized identifier generation policy (based for example on enum + classes or preprocessing steps) may represent yet another option. + +These are just some examples of possible approaches to the problem but there are +many others. As already mentioned above, since users have full control over +their types, this problem is in any case easy to solve and should not worry too +much.
+In all likelihood, it will never happen to run into a conflict anyway. + +## Type traits + +A handful of utilities and traits not present in the standard template library +but which can be useful in everyday life.
+This list **is not** exhaustive and contains only some of the most useful +classes. Refer to the inline documentation for more information on the features +offered by this module. + +### Size of + +The standard operator `sizeof` complains when users provide it for example with +function or incomplete types. On the other hand, it's guaranteed that its result +is always nonzero, even if applied to an empty class type.
+This small class combines the two and offers an alternative to `sizeof` that +works under all circumstances, returning zero if the type isn't supported: + +```cpp +const auto size = entt::size_of_v; +``` + +### Is applicable + +The standard library offers the great `std::is_invocable` trait in several +forms. This takes a function type and a series of arguments and returns true if +the condition is satisfied.
+Moreover, users are also provided with `std::apply`, a tool for combining +invocable elements and tuples of arguments. + +It would therefore be a good idea to have a variant of `std::is_invocable` that +also accepts its arguments in the form of a tuple-like type, so as to complete +the offer: + +```cpp +constexpr bool result = entt::is_applicable>; +``` + +This trait is built on top of `std::is_invocable` and does nothing but unpack a +tuple-like type and simplify the code at the call site. + +### Constness as + +An utility to easily transfer the constness of a type to another type: + +```cpp +// type is const dst_type because of the constness of src_type +using type = entt::constness_as_t; +``` + +The trait is subject to the rules of the language. Therefore, for example, +transferring constness between references won't give the desired effect. + +### Member class type + +The `auto` template parameter introduced with C++17 made it possible to simplify +many class templates and template functions but also made the class type opaque +when members are passed as template arguments.
+The purpose of this utility is to extract the class type in a few lines of code: + +```cpp +template +using clazz = entt::member_class_t; +``` + +### Integral constant + +Since `std::integral_constant` may be annoying because of its form that requires +to specify both a type and a value of that type, there is a more user-friendly +shortcut for the creation of integral constants.
+This shortcut is the alias template `entt::integral_constant`: + +```cpp +constexpr auto constant = entt::integral_constant<42>; +``` + +Among the other uses, when combined with a hashed string it helps to define tags +as human-readable _names_ where actual types would be required otherwise: + +```cpp +constexpr auto enemy_tag = entt::integral_constant<"enemy"_hs>; +registry.emplace(entity); +``` + +### Tag + +Since `id_type` is very important and widely used in `EnTT`, there is a more +user-friendly shortcut for the creation of integral constants based on it.
+This shortcut is the alias template `entt::tag`. + +If used in combination with hashed strings, it helps to use human-readable names +where types would be required otherwise. As an example: + +```cpp +registry.emplace>(entity); +``` + +However, this isn't the only permitted use. Literally any value convertible to +`id_type` is a good candidate, such as the named constants of an unscoped enum. + +### Type list and value list + +There is no respectable library where the much desired _type list_ can be +missing.
+`EnTT` is no exception and provides (making extensive use of it internally) the +`type_list` type, in addition to its `value_list` counterpart dedicated to +non-type template parameters. + +Here is a (possibly incomplete) list of the functionalities that come with a +type list: + +* `type_list_element[_t]` to get the N-th element of a type list. +* `type_list_cat[_t]` and a handy `operator+` to concatenate type lists. +* `type_list_unique[_t]` to remove duplicate types from a type list. +* `type_list_contains[_v]` to know if a type list contains a given type. +* `type_list_diff[_t]` to remove types from type lists. + +I'm also pretty sure that more and more utilities will be added over time as +needs become apparent.
+Many of these functionalities also exist in their version dedicated to value +lists. We therefore have `value_list_element[_v]` as well as +`value_list_cat[_t]`and so on. + +# Unique sequential identifiers + +Sometimes it's useful to be able to give unique, sequential numeric identifiers +to types either at compile-time or runtime.
+There are plenty of different solutions for this out there and I could have used +one of them. However, I decided to spend my time to define a couple of tools +that fully embraces what the modern C++ has to offer. + +## Compile-time generator + +To generate sequential numeric identifiers at compile-time, `EnTT` offers the +`identifier` class template: + +```cpp +// defines the identifiers for the given types +using id = entt::identifier; + +// ... + +switch(a_type_identifier) { +case id::type: + // ... + break; +case id::type: + // ... + break; +default: + // ... +} +``` + +This is all what this class template has to offer: a `type` inline variable that +contains a numeric identifier for the given type. It can be used in any context +where constant expressions are required. + +As long as the list remains unchanged, identifiers are also guaranteed to be +stable across different runs. In case they have been used in a production +environment and a type has to be removed, one can just use a placeholder to left +the other identifiers unchanged: + +```cpp +template struct ignore_type {}; + +using id = entt::identifier< + a_type_still_valid, + ignore_type, + another_type_still_valid +>; +``` + +Perhaps a bit ugly to see in a codebase but it gets the job done at least. + +## Runtime generator + +To generate sequential numeric identifiers at runtime, `EnTT` offers the +`family` class template: + +```cpp +// defines a custom generator +using id = entt::family; + +// ... + +const auto a_type_id = id::type; +const auto another_type_id = id::type; +``` + +This is all what a _family_ has to offer: a `type` inline variable that contains +a numeric identifier for the given type.
+The generator is customizable, so as to get different _sequences_ for different +purposes if needed. + +Please, note that identifiers aren't guaranteed to be stable across different +runs. Indeed it mostly depends on the flow of execution. + +# Utilities + +It's not possible to escape the temptation to add utilities of some kind to a +library. In fact, `EnTT` also provides a handful of tools to simplify the +life of developers: + +* `entt::identity`: the identity function object that will be available with + C++20. It returns its argument unchanged and nothing more. It's useful as a + sort of _do nothing_ function in template programming. + +* `entt::overload`: a tool to disambiguate different overloads from their + function type. It works with both free and member functions.
+ Consider the following definition: + + ```cpp + struct clazz { + void bar(int) {} + void bar() {} + }; + ``` + + This utility can be used to get the _right_ overload as: + + ```cpp + auto *member = entt::overload(&clazz::bar); + ``` + + The line above is literally equivalent to: + + ```cpp + auto *member = static_cast(&clazz::bar); + ``` + + Just easier to read and shorter to type. + +* `entt::overloaded`: a small class template used to create a new type with an + overloaded `operator()` from a bunch of lambdas or functors.
+ As an example: + + ```cpp + entt::overloaded func{ + [](int value) { /* ... */ }, + [](char value) { /* ... */ } + }; + + func(42); + func('c'); + ``` + + Rather useful when doing metaprogramming and having to pass to a function a + callable object that supports multiple types at once. + +* `entt::y_combinator`: this is a C++ implementation of **the** _y-combinator_. + If it's not clear what it is, there is probably no need for this utility.
+ Below is a small example to show its use: + + ```cpp + entt::y_combinator gauss([](const auto &self, auto value) -> unsigned int { + return value ? (value + self(value-1u)) : 0; + }); + + const auto result = gauss(3u); + ``` + + Maybe convoluted at a first glance but certainly effective. Unfortunately, + the language doesn't make it possible to do much better. + +This is a rundown of the (actually few) utilities made available by `EnTT`. The +list will probably grow over time but the size of each will remain rather small, +as has been the case so far. diff --git a/modules/entt/docs/md/entity.md b/modules/entt/docs/md/entity.md index df59a4b..cf40bb7 100644 --- a/modules/entt/docs/md/entity.md +++ b/modules/entt/docs/md/entity.md @@ -7,34 +7,47 @@ * [Introduction](#introduction) * [Design decisions](#design-decisions) - * [A bitset-free entity-component system](#a-bitset-free-entity-component-system) + * [Type-less and bitset-free](#type-less-and-bitset-free) + * [Build your own](#build-your-own) * [Pay per use](#pay-per-use) * [All or nothing](#all-or-nothing) - * [Stateless systems](#stateless-systems) * [Vademecum](#vademecum) +* [Pools](#pools) * [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component) * [Observe changes](#observe-changes) - * [Runtime components](#runtime-components) - * [A journey through a plugin](#a-journey-through-a-plugin) + * [They call me Reactive System](#they-call-me-reactive-system) * [Sorting: is it possible?](#sorting-is-it-possible) + * [Helpers](#helpers) + * [Null entity](#null-entity) + * [Tombstone](#tombstone) + * [To entity](#to-entity) + * [Dependencies](#dependencies) + * [Invoke](#invoke) + * [Handle](#handle) + * [Organizer](#organizer) + * [Context variables](#context-variables) + * [Aliased properties](#aliased-properties) + * [Component traits](#component-traits) + * [Pointer stability](#pointer-stability) + * [In-place delete](#in-place-delete) + * [Hierarchies and the like](#hierarchies-and-the-like) + * [Meet the runtime](#meet-the-runtime) + * [A base class to rule them all](#a-base-class-to-rule-them-all) + * [Beam me up, registry](#beam-me-up-registry) * [Snapshot: complete vs continuous](#snapshot-complete-vs-continuous) * [Snapshot loader](#snapshot-loader) * [Continuous loader](#continuous-loader) * [Archives](#archives) * [One example to rule them all](#one-example-to-rule-them-all) - * [Prototype](#prototype) - * [Helpers](#helpers) - * [Dependency function](#dependency-function) - * [Tags](#tags) - * [Null entity](#null-entity) - * [Context variables](#context-variables) * [Views and Groups](#views-and-groups) * [Views](#views) - * [Runtime views](#runtime-views) + * [View pack](#view-pack) + * [Runtime views](#runtime-views) * [Groups](#groups) * [Full-owning groups](#full-owning-groups) * [Partial-owning groups](#partial-owning-groups) * [Non-owning groups](#non-owning-groups) + * [Nested groups](#nested-groups) * [Types: const, non-const and all in between](#types-const-non-const-and-all-in-between) * [Give me everything](#give-me-everything) * [What is allowed and what is not](#what-is-allowed-and-what-is-not) @@ -42,6 +55,8 @@ * [Empty type optimization](#empty-type-optimization) * [Multithreading](#multithreading) * [Iterators](#iterators) + * [Const registry](#const-registry) +* [Beyond this document](#beyond-this-document) @@ -55,10 +70,10 @@ used mostly in game development. # Design decisions -## A bitset-free entity-component system +## Type-less and bitset-free -`EnTT` is a _bitset-free_ entity-component system that doesn't require users to -specify the component set at compile-time.
+`EnTT` offers a sparse set based model that doesn't require users to specify the +set of components neither at compile-time nor at runtime.
This is why users can instantiate the core class simply like: ```cpp @@ -71,6 +86,20 @@ In place of its more annoying and error-prone counterpart: entt::registry registry; ``` +Furthermore, it isn't necessary to announce the existence of a component type. +When the time comes, just use it and that's all. + +## Build your own + +`EnTT` is designed as a container that can be used at any time, just like a +vector or any other container. It doesn't attempt in any way to take over on the +user codebase, nor to control its main loop or process scheduling.
+Unlike other more or less well known models, it also makes use of independent +pools that can be extended via _static mixins_. The built-in signal support is +an example of this flexible model: defined as a mixin, it's easily disabled if +not needed. Similarly, the storage class has a specialization that shows how +everything is customizable down to the smallest detail. + ## Pay per use `EnTT` is entirely designed around the principle that users have to pay only for @@ -82,88 +111,69 @@ Even worse, some approaches tend to heavily affect other functionalities like the construction and destruction of components to favor iterations, even when it isn't strictly required. In fact, slightly worse performance along non-critical paths are the right price to pay to reduce memory usage and have overall better -perfomance sometimes and I've always wondered why this kind of tools do not -leave me the choice.
+performance.
`EnTT` follows a completely different approach. It gets the best out from the basic data structures and gives users the possibility to pay more for higher -performance where needed.
-The disadvantage of this approach is that users need to know the systems they -are working on and the tools they are using. Otherwise, the risk to ruin the -performance along critical paths is high. +performance where needed. So far, this choice has proven to be a good one and I really hope it can be for many others besides me. ## All or nothing -`EnTT` is such that at every moment a pair `(T *, size)` is available to -directly access all the instances of a given component type `T`.
-This was a guideline and a design decision that influenced many choices, for -better and for worse. I cannot say whether it will be useful or not to the -reader, but it's worth to mention it, because it's one of the corner stones of -this library. - -Many of the tools described below, from the registry to the views and up to the -groups give the possibility to get this information and have been designed -around this need, which was and remains one of my main requirements during the -development.
+`EnTT` is such that a `T**` pointer (or whatever a custom pool returns) is +always available to directly access all the instances of a given component type +`T`.
+I cannot say whether it will be useful or not to the reader, but it's worth to +mention it since it's one of the corner stones of this library. + +Many of the tools described below give the possibility to get this information +and have been designed around this need.
The rest is experimentation and the desire to invent something new, hoping to have succeeded. -## Stateless systems - -`EnTT` is designed so that it can work with _stateless systems_. In other words, -all systems can be free functions and there is no need to define them as classes -(although nothing prevents users from doing so).
-This is possible because the main class with which the users will work provides -all what is needed to act as the sole _source of truth_ of an application. - -To be honest, this point became part of the design principles at a later date, -but has also become one of the cornerstones of the library to date, as stateless -systems are widely used and appreciated in general. - # Vademecum The registry to store, the views and the groups to iterate. That's all. -An entity (the _E_ of an _ECS_) is an opaque identifier that users should just -use as-is and store around if needed. Do not try to inspect an entity -identifier, its format can change in future and a registry offers all the -functionalities to query them out-of-the-box. The underlying type of an entity -(either `std::uint16_t`, `std::uint32_t` or `std::uint64_t`) can be specified -when defining a registry (actually `entt::registry` is nothing more than an -alias for `entt::basic_registry` and `entt::entity` is an alias -for `std::uint32_t`).
-Components (the _C_ of an _ECS_) should be plain old data structures or more -complex and movable data structures with a proper constructor. Actually, the -sole requirement of a component type is that it must be both move constructible -and move assignable. They are list initialized by using the parameters provided -to construct the component itself. No need to register components or their types -neither with the registry nor with the entity-component system at all.
-Systems (the _S_ of an _ECS_) are just plain functions, functors, lambdas or -whatever users want. They can accept a registry, a view or a group of any type -and use them the way they prefer. No need to register systems or their types -neither with the registry nor with the entity-component system at all. - -The following sections will explain in short how to use the entity-component -system, the core part of the whole library.
-In fact, the project is composed of many other classes in addition to those -describe below. For more details, please refer to the inline documentation. +The `entt::entity` type implements the concept of _entity identifier_. An entity +(the _E_ of an _ECS_) is an opaque element to use as-is. Inspecting it isn't +recommended since its format can change in future.
+Components (the _C_ of an _ECS_) are both move constructible and move assignable +types. No need to register them nor their types.
+Systems (the _S_ of an _ECS_) can be plain functions, functors, lambdas and so +on. It's not required to announce them in any case and have no requirements. + +The next sections go into detail on how to use the entity-component system part +of the `EnTT` library.
+The project is composed of many other classes in addition to those described +below. For more details, please refer to the inline documentation. + +# Pools + +Pools of components are a sort of _specialized version_ of a sparse set. Each +pool contains all the instances of a single component type and all the entities +to which it's assigned.
+Sparse arrays are _paged_ to avoid wasting memory. Packed arrays of components +are also paged to have pointer stability upon additions. Packed arrays of +entities are not instead.
+All pools rearranges their items in order to keep the internal arrays tightly +packed and maximize performance, unless pointer stability is enabled. # The Registry, the Entity and the Component -A registry can store and manage entities, as well as create views and groups to -iterate the underlying data structures.
+A registry stores and manages entities (or better, identifiers) and pools.
The class template `basic_registry` lets users decide what's the preferred type to represent an entity. Because `std::uint32_t` is large enough for almost all -the cases, there exists also the alias `entt::entity` for it, as well as the -alias `entt::registry` for `entt::basic_registry`. +the cases, there also exists the enum class `entt::entity` that _wraps_ it and +the alias `entt::registry` for `entt::basic_registry`. -Entities are represented by _entity identifiers_. An entity identifier is an -opaque type that users should not inspect or modify in any way. It carries -information about the entity itself and its version. +Entities are represented by _entity identifiers_. An entity identifier contains +information about the entity itself and its version.
+User defined identifiers are allowed as enum classes and class types that define +an `entity_type` member of type `std::uint32_t` or `std::uint64_t`. -A registry can be used both to construct and to destroy entities: +A registry is used both to construct and to destroy entities: ```cpp // constructs a naked entity with no components and returns its identifier @@ -173,9 +183,9 @@ auto entity = registry.create(); registry.destroy(entity); ``` -There exists also an overload of the `create` and `destroy` member functions -that accepts two iterators, that is a range to assign or to destroy. It can be -used to create or destroy multiple entities at once: +The `create` member function also accepts a hint and has an overload that gets +two iterators and can be used to generate multiple entities at once efficiently. +Similarly, the `destroy` member function also works with a range of entities: ```cpp // destroys all the entities in a range @@ -183,17 +193,23 @@ auto view = registry.view(); registry.destroy(view.begin(), view.end()); ``` -In both cases, the `create` member function accepts also a list of default -constructible types of components to assign to the entities before to return. -It's a faster alternative to the creation and subsequent assignment of -components in separate steps. +In addition to offering an overload to force the version upon destruction. Note +that this function removes all components from an entity before releasing its +identifier. There also exists a _lighter_ alternative that only releases the +elements without poking in any pool, for use with orphaned entities: + +```cpp +// releases an orphaned identifier +registry.release(entity); +``` + +As with the `destroy` function, also in this case entity ranges are supported +and it's possible to force the version during release. -When an entity is destroyed, the registry can freely reuse it internally with a -slightly different identifier. In particular, the version of an entity is -increased each and every time it's discarded.
-In case entity identifiers are stored around, the registry offers all the -functionalities required to test them and to get out of them the information -they carry: +In both cases, when an identifier is released, the registry can freely reuse it +internally. In particular, the version of an entity is increased (unless the +overload that forces a version is used instead of the default one).
+Users can probe an identifier to know the information it carries: ```cpp // returns true if the entity is still valid, false otherwise @@ -206,104 +222,118 @@ auto version = registry.version(entity); auto curr = registry.current(entity); ``` -Components can be assigned to or removed from entities at any time with a few -calls to member functions of the registry. As for the entities, the registry -offers also a set of functionalities users can use to work with the components. +Components can be assigned to or removed from entities at any time. As for the +entities, the registry offers a set of functions to use to work with components. -The `assign` member function template creates, initializes and assigns to an -entity the given component. It accepts a variable number of arguments to +The `emplace` member function template creates, initializes and assigns to an +entity the given component. It accepts a variable number of arguments to use to construct the component itself if present: ```cpp -registry.assign(entity, 0., 0.); +registry.emplace(entity, 0., 0.); // ... -auto &velocity = registry.assign(entity); +auto &vel = registry.emplace(entity); vel.dx = 0.; vel.dy = 0.; ``` -If an entity already has the given component, the `replace` member function -template can be used to replace it: +The default storage _detects_ aggregate types internally and exploits aggregate +initialization when possible.
+Therefore, it's not strictly necessary to define a constructor for each type, in +accordance with the rules of the language. -```cpp -registry.replace(entity, 0., 0.); +On the other hand, `insert` works with _ranges_ and can be used to: -// ... +* Assign the same component to all entities at once when a type is specified as + a template parameter or an instance is passed as an argument: -auto &velocity = registry.replace(entity); -vel.dx = 0.; -vel.dy = 0.; -``` + ```cpp + // default initialized type assigned by copy to all entities + registry.insert(first, last); -In case users want to assign a component to an entity, but it's unknown whether -the entity already has it or not, `assign_or_replace` does the work in a single -call (there is a performance penalty to pay for this mainly due to the fact that -it has to check if the entity already has the given component or not): + // user-defined instance assigned by copy to all entities + registry.insert(from, to, position{0., 0.}); + ``` + +* Assign a set of components to the entities when a range is provided (the + length of the range of components must be the same of that of entities): + + ```cpp + // first and last specify the range of entities, instances points to the first element of the range of components + registry.insert(first, last, instances); + ``` + +If an entity already has the given component, the `replace` and `patch` member +function templates can be used to update it: ```cpp -registry.assign_or_replace(entity, 0., 0.); +// replaces the component in-place +registry.patch(entity, [](auto &pos) { pos.x = pos.y = 0.; }); -// ... +// constructs a new instance from a list of arguments and replaces the component +registry.replace(entity, 0., 0.); +``` -auto &velocity = registry.assign_or_replace(entity); -vel.dx = 0.; -vel.dy = 0.; +When it's unknown whether an entity already owns an instance of a component, +`emplace_or_replace` is the function to use instead: + +```cpp +registry.emplace_or_replace(entity, 0., 0.); ``` -Note that `assign_or_replace` is a slightly faster alternative for the following -`if/else` statement and nothing more: +This is a slightly faster alternative for the following snippet: ```cpp -if(registry.has(entity)) { - registry.replace(entity, arg1, argN); +if(registry.all_of(entity)) { + registry.replace(entity, 0., 0.); } else { - registry.assign(entity, arg1, argN); + registry.emplace(entity, 0., 0.); } ``` -As already shown, if in doubt about whether or not an entity has one or more -components, the `has` member function template may be useful: +The `all_of` and `any_of` member functions may also be useful if in doubt about +whether or not an entity has all the components in a set or any of them: ```cpp -bool b = registry.has(entity); +// true if entity has all the given components +bool all = registry.all_of(entity); + +// true if entity has at least one of the given components +bool any = registry.any_of(entity); ``` -On the other side, if the goal is to delete a single component, the `remove` -member function template is the way to go when it's certain that the entity owns -a copy of the component: +If the goal is to delete a component from an entity that owns it, the `erase` +member function template is the way to go: ```cpp -registry.remove(entity); +registry.erase(entity); ``` -Otherwise consider to use the `reset` member function. It behaves similarly to -`remove` but with a strictly defined behavior (and a performance penalty is the -price to pay for this). In particular it removes the component if and only if it -exists, otherwise it returns safely to the caller: +When in doubt whether the entity owns the component, use the `remove` member +function instead. It behaves similarly to `erase` but it erases the component +if and only if it exists, otherwise it returns safely to the caller: ```cpp -registry.reset(entity); +registry.remove(entity); ``` -There exist also two other _versions_ of the `reset` member function: +The `clear` member function works similarly and can be used to either: -* If no entity is passed to it, `reset` will remove the given component from - each entity that has it: +* Erases all instances of the given components from the entities that own them: ```cpp - registry.reset(); + registry.clear(); ``` -* If neither the entity nor the component are specified, all the entities still - in use and their components are destroyed: +* Or destroy all entities in a registry at once: ```cpp - registry.reset(); + registry.clear(); ``` -Finally, references to components can be retrieved simply by doing this: +Finally, references to components can be retrieved simply as: ```cpp const auto &cregistry = registry; @@ -313,8 +343,8 @@ const auto &crenderable = cregistry.get(entity); auto &renderable = registry.get(entity); // const and non-const references -const auto &[cpos, cvel] = cregistry.get(entity); -auto &[pos, vel] = registry.get(entity); +const auto [cpos, cvel] = cregistry.get(entity); +auto [pos, vel] = registry.get(entity); ``` The `get` member function template gives direct access to the component of an @@ -324,151 +354,201 @@ the component owned by an entity if any, a null pointer otherwise. ## Observe changes -Because of how the registry works internally, it stores a bunch of signal -handlers for each pool in order to notify some of its data structures on the -construction and destruction of components or when an instance of a component is -explicitly replaced by the user.
-These signal handlers are also exposed and made available to users. These are -the basic bricks to build fancy things like dependencies and reactive systems. +By default, each storage comes with a mixin that adds signal support to it.
+This allows for fancy things like dependencies and reactive systems. -To get a sink to be used to connect and disconnect listeners so as to be -notified on the creation of a component, use the `on_construct` member function: +The `on_construct` member function returns a _sink_ (which is an object for +connecting and disconnecting listeners) for those interested in notifications +when a new instance of a given component type is created: ```cpp // connects a free function registry.on_construct().connect<&my_free_function>(); // connects a member function -registry.on_construct().connect<&my_class::member>(&instance); +registry.on_construct().connect<&my_class::member>(instance); // disconnects a free function registry.on_construct().disconnect<&my_free_function>(); // disconnects a member function -registry.on_construct().disconnect<&my_class::member>(&instance); +registry.on_construct().disconnect<&my_class::member>(instance); ``` -To be notified when components are destroyed, use the `on_destroy` member -function instead. Finally, the `on_replace` member function will return a sink -to which to connect listeners to observe changes on components. +Similarly, `on_destroy` and `on_update` are used to receive notifications about +the destruction and update of an instance, respectively.
+Because of how C++ works, listeners attached to `on_update` are only invoked +following a call to `replace`, `emplace_or_replace` or `patch`. -The function type of a listener for the construction signal should be equivalent -to the following: - -```cpp -void(entt::registry &, entt::entity, Component &); -``` - -Where `Component` is intuitively the type of component of interest. In other -words, a listener is provided with the registry that triggered the notification -and the entity affected by the change, in addition to the newly created -instance.
-The sink returned by the `on_replace` member function accepts listeners the -signature of which is the same of that of the construction signal. The one of -the destruction signal is also similar, except for the `Component` parameter: +The function type of a listener is equivalent to the following: ```cpp void(entt::registry &, entt::entity); ``` -This is mainly due to performance reasons. While the component is made available -after the construction, it is not when destroyed. Because of that, there are no -reasons to get it from the underlying storage unless the user requires so. In -this case, the registry is made available for the purpose. +In all cases, listeners are provided with the registry that triggered the +notification and the involved entity. Note also that: -* Listeners for the construction signal are invoked **after** components have +* Listeners for the construction signals are invoked **after** components have been assigned to entities. -* Listeners designed to observe changes are invoked **before** components have - been replaced and therefore before newly created instances have been assigned - to entities. -* Listeners for the destruction signal are invoked **before** components have + +* Listeners designed to observe changes are invoked **after** components have + been updated. + +* Listeners for the destruction signals are invoked **before** components have been removed from entities. -* The order of invocation of the listeners isn't guaranteed in any case. -There are also some limitations on what a listener can and cannot do. In -particular: +There are also some limitations on what a listener can and cannot do: * Connecting and disconnecting other functions from within the body of a listener should be avoided. It can lead to undefined behavior in some cases. + +* Removing the component from within the body of a listener that observes the + construction or update of instances of a given type isn't allowed. + * Assigning and removing components from within the body of a listener that observes the destruction of instances of a given type should be avoided. It can lead to undefined behavior in some cases. This type of listeners is intended to provide users with an easy way to perform cleanup and nothing more. -To a certain extent, these limitations don't apply. However, it's risky to try -to force them and users should respect the limitations unless they know exactly -what they are doing. Subtle bugs are the price to pay in case of errors -otherwise. +Please, refer to the documentation of the signal class to know about all the +features it offers.
+There are many useful but less known functionalities that aren't described here, +such as the connection objects or the possibility to attach listeners with a +list of parameters that is shorter than that of the signal itself. -In general, events and therefore listeners must not be used as replacements for -systems. They should not contain much logic and interactions with a registry -should be kept to a minimum, if possible. Note also that the greater the number -of listeners, the greater the performance hit when components are created or -destroyed. +### They call me Reactive System -## Runtime components +Signals are the basic tools to construct reactive systems, even if they aren't +enough on their own. `EnTT` tries to take another step in that direction with +the `observer` class template.
+In order to explain what reactive systems are, this is a slightly revised quote +from the documentation of the library that first introduced this tool, +[Entitas](https://github.com/sschmid/Entitas-CSharp): -Defining components at runtime is useful to support plugin systems and mods in -general. However, it seems impossible with a tool designed around a bunch of -templates. Indeed it's not that difficult.
-Of course, some features cannot be easily exported into a runtime -environment. As an example, sorting a group of components defined at runtime -isn't for free if compared to most of the other operations. However, the basic -functionalities of an entity-component system such as `EnTT` fit the problem -perfectly and can also be used to manage runtime components if required.
-All that is necessary to do it is to know the identifiers of the components. An -identifier is nothing more than a number or similar that can be used at runtime -to work with the type system. +>Imagine you have 100 fighting units on the battlefield but only 10 of them +>changed their positions. Instead of using a normal system and updating all 100 +>entities depending on the position, you can use a reactive system which will +>only update the 10 changed units. So efficient. -In `EnTT`, identifiers are easily accessible: +In `EnTT`, this means to iterating over a reduced set of entities and components +with respect to what would otherwise be returned from a view or a group.
+On these words, however, the similarities with the proposal of `Entitas` also +end. The rules of the language and the design of the library obviously impose +and allow different things. + +An `observer` is initialized with an instance of a registry and a set of rules +that describes what are the entities to intercept. As an example: ```cpp -entt::registry registry; +entt::observer observer{registry, entt::collector.update()}; +``` + +The class is default constructible and can be reconfigured at any time by means +of the `connect` member function. Moreover, instances can be disconnected from +the underlying registries through the `disconnect` member function.
+The `observer` offers also what is needed to query the internal state and to +know if it's empty or how many entities it contains. Moreover, it can return a +raw pointer to the list of entities it contains. + +However, the most important features of this class are that: + +* It's iterable and therefore users can easily walk through the list of entities + by means of a range-for loop or the `each` member function. -// component identifier -auto type = registry.type(); +* It's clearable and therefore users can consume the entities and literally + reset the observer after each iteration. + +These aspects make the observer an incredibly powerful tool to know at any time +what are the entities that matched the given rules since the last time one +asked: + +```cpp +for(const auto entity: observer) { + // ... +} + +observer.clear(); ``` -Once the identifiers are made available, almost everything becomes pretty -simple. +The snippet above is equivalent to the following: -### A journey through a plugin +```cpp +observer.each([](const auto entity) { + // ... +}); +``` -`EnTT` comes with an example (actually a test) that shows how to integrate -compile-time and runtime components in a stack based JavaScript environment. It -uses [`Duktape`](https://github.com/svaarala/duktape) under the hood, mainly -because I wanted to learn how it works at the time I was writing the code. +At least as long as the `observer` isn't const. This means that the non-const +overload of `each` does also reset the underlying data structure before to +return to the caller, while the const overload does not for obvious reasons. + +The `collector` is an utility aimed to generate a list of `matcher`s (the actual +rules) to use with an `observer` instead.
+There are two types of `matcher`s: + +* Observing matcher: an observer will return at least all the living entities + for which one or more of the given components have been updated and not yet + destroyed. + + ```cpp + entt::collector.update(); + ``` + + _Updated_ in this case means that all listeners attached to `on_update` are + invoked. In order for this to happen, specific functions such as `patch` must + be used. Refer to the specific documentation for more details. + +* Grouping matcher: an observer will return at least all the living entities + that would have entered the given group if it existed and that would have + not yet left it. + + ```cpp + entt::collector.group(entt::exclude); + ``` + + A grouping matcher supports also exclusion lists as well as single components. -The code is not production-ready and overall performance can be highly improved. -However, I sacrificed optimizations in favor of a more readable piece of code. I -hope I succeeded.
-Note also that this isn't neither the only nor (probably) the best way to do it. -In fact, the right way depends on the scripting language and the problem one is -facing in general.
-That being said, feel free to use it at your own risk. - -The basic idea is that of creating a compile-time component aimed to map all the -runtime components assigned to an entity.
-Identifiers come in use to address the right function from a map when invoked -from the runtime environment and to filter entities when iterating.
-With a bit of gymnastic, one can narrow views and improve the performance to -some extent but it was not the goal of the example. +Roughly speaking, an observing matcher intercepts the entities for which the +given components are updated while a grouping matcher tracks the entities that +have assigned the given components since the last time one asked.
+If an entity already has all the components except one and the missing type is +assigned to it, the entity is intercepted by a grouping matcher. + +In addition, a matcher can be filtered with a `where` clause: + +```cpp +entt::collector.update().where(entt::exclude); +``` + +This clause introduces a way to intercept entities if and only if they are +already part of a hypothetical group. If they are not, they aren't returned by +the observer, no matter if they matched the given rule.
+In the example above, whenever the component `sprite` of an entity is updated, +the observer probes the entity itself to verify that it has at least `position` +and has not `velocity` before to store it aside. If one of the two conditions of +the filter isn't respected, the entity is discarded, no matter what. + +A `where` clause accepts a theoretically unlimited number of types as well as +multiple elements in the exclusion list. Moreover, every matcher can have its +own clause and multiple clauses for the same matcher are combined in a single +one. ## Sorting: is it possible? -It goes without saying that sorting entities and components is possible with -`EnTT`.
-In fact, there are two functions that respond to slightly different needs: +Sorting entities and components is possible with `EnTT`. In particular, it uses +an in-place algorithm that doesn't require memory allocations nor anything else +and is therefore particularly convenient.
+There are two functions that respond to slightly different needs: * Components can be sorted either directly: ```cpp registry.sort([](const auto &lhs, const auto &rhs) { return lhs.z < rhs.z; - }); ``` @@ -480,12 +560,9 @@ In fact, there are two functions that respond to slightly different needs: }); ``` - There exists also the possibility to use a custom sort function object, as - long as it adheres to the requirements described in the inline - documentation.
- This is possible mainly because users can get much more with a custom sort - function object if the usage pattern is known. As an example, in case of an - almost sorted pool, quick sort could be much, much slower than insertion sort. + There exists also the possibility to use a custom sort function object for + when the usage pattern is known. As an example, in case of an almost sorted + pool, quick sort could be much slower than insertion sort. * Components can be sorted according to the order imposed by another component: @@ -496,6 +573,616 @@ In fact, there are two functions that respond to slightly different needs: In this case, instances of `movement` are arranged in memory so that cache misses are minimized when the two components are iterated together. +As a side note, the use of groups limits the possibility of sorting pools of +components. Refer to the specific documentation for more details. + +## Helpers + +The so called _helpers_ are small classes and functions mainly designed to offer +built-in support for the most basic functionalities. + +### Null entity + +The `entt::null` variable models the concept of _null entity_.
+The library guarantees that the following expression always returns false: + +```cpp +registry.valid(entt::null); +``` + +A registry rejects the null entity in all cases because it isn't considered +valid. It also means that the null entity cannot own components.
+The type of the null entity is internal and should not be used for any purpose +other than defining the null entity itself. However, there exist implicit +conversions from the null entity to identifiers of any allowed type: + +```cpp +entt::entity null = entt::null; +``` + +Similarly, the null entity can be compared to any other identifier: + +```cpp +const auto entity = registry.create(); +const bool null = (entity == entt::null); +``` + +As for its integral form, the null entity only affects the entity part of an +identifier and is instead completely transparent to its version. + +Be aware that `entt::null` and entity 0 aren't the same thing. Likewise, a zero +initialized entity isn't the same as `entt::null`. Therefore, although +`entt::entity{}` is in some sense an alias for entity 0, none of them can be +used to create a null entity. + +### Tombstone + +Similar to the null entity, the `entt::tombstone` variable models the concept of +_tombstone_.
+Once created, the integral form of the two values is the same, although they +affect different parts of an identifier. In fact, the tombstone only uses the +version part of it and is completely transparent to the entity part. + +Also in this case, the following expression always returns false: + +```cpp +registry.valid(entt::tombstone); +``` + +Moreover, users cannot set the tombstone version when releasing an entity: + +```cpp +registry.destroy(entity, entt::tombstone); +``` + +In this case, a different version number is implicitly generated.
+The type of a tombstone is internal and can change at any time. However, there +exist implicit conversions from a tombstone to identifiers of any allowed type: + +```cpp +entt::entity null = entt::tombstone; +``` + +Similarly, the tombstone can be compared to any other identifier: + +```cpp +const auto entity = registry.create(); +const bool tombstone = (entity == entt::tombstone); +``` + +Be aware that `entt::tombstone` and entity 0 aren't the same thing. Likewise, a +zero initialized entity isn't the same as `entt::tombstone`. Therefore, although +`entt::entity{}` is in some sense an alias for entity 0, none of them can be +used to create tombstones. + +### To entity + +Sometimes it's useful to get the entity from a component instance.
+This is what the `entt::to_entity` helper does. It accepts a registry and an +instance of a component and returns the entity associated with the latter: + +```cpp +const auto entity = entt::to_entity(registry, position); +``` + +A null entity is returned in case the component doesn't belong to the registry. + +### Dependencies + +The `registry` class is designed to be able to create short circuits between its +functions. This simplifies the definition of _dependencies_ between different +operations.
+For example, the following adds (or replaces) the component `a_type` whenever +`my_type` is assigned to an entity: + +```cpp +registry.on_construct().connect<&entt::registry::emplace_or_replace>(); +``` + +Similarly, the code shown below removes `a_type` from an entity whenever +`my_type` is assigned to it: + +```cpp +registry.on_construct().connect<&entt::registry::remove>(); +``` + +A dependency can also be easily broken as follows: + +```cpp +registry.on_construct().disconnect<&entt::registry::emplace_or_replace>(); +``` + +There are many other types of dependencies. In general, most of the functions +that accept an entity as the first argument are good candidates for this +purpose. + +### Invoke + +Sometimes it's useful to directly invoke a member function of a component as a +callback. It's already possible in practice but requires users to _extend_ their +classes and this may not always be possible.
+The `invoke` helper allows to _propagate_ the signal in these cases: + +```cpp +registry.on_construct().connect>(); +``` + +All it does is pick up the _right_ component for the received entity and invoke +the requested method, passing on the arguments if necessary. + +### Handle + +A handle is a thin wrapper around an entity and a registry. It provides the same +functions that the registry offers for working with components, such as +`emplace`, `get`, `patch`, `remove` and so on. The difference being that the +entity is implicitly passed to the registry.
+It's default constructible as an invalid handle that contains a null registry +and a null entity. When it contains a null registry, calling functions that +delegate execution to the registry will cause an undefined behavior, so it's +recommended to check the validity of the handle with implicit cast to `bool` +when in doubt.
+A handle is also non-owning, meaning that it can be freely copied and moved +around without affecting its entity (in fact, handles happen to be trivially +copyable). An implication of this is that mutability becomes part of the +type. + +There are two aliases that use `entt::entity` as their default entity: +`entt::handle` and `entt::const_handle`.
+Users can also easily create their own aliases for custom identifiers as: + +```cpp +using my_handle = entt::basic_handle; +using my_const_handle = entt::basic_handle; +``` + +Handles are also implicitly convertible to const handles out of the box but not +the other way around.
+A handle stores a non-const pointer to a registry and therefore it can do all +the things that can be done with a non-const registry. On the other hand, a +const handles store const pointers to registries and offer a restricted set of +functionalities. + +This class is intended to simplify function signatures. In case of functions +that take a registry and an entity and do most of their work on that entity, +users might want to consider using handles, either const or non-const. + +### Organizer + +The `organizer` class template offers support for creating an execution graph +from a set of functions and their requirements on resources.
+The resulting tasks aren't executed in any case. This isn't the goal of this +tool. Instead, they are returned to the user in the form of a graph that allows +for safe execution. + +All functions are added in order of execution to the organizer: + +```cpp +entt::organizer organizer; + +// adds a free function to the organizer +organizer.emplace<&free_function>(); + +// adds a member function and an instance on which to invoke it to the organizer +clazz instance; +organizer.emplace<&clazz::member_function>(&instance); + +// adds a decayed lambda directly +organizer.emplace(+[](const void *, entt::registry &) { /* ... */ }); +``` + +These are the parameters that a free function or a member function can accept: + +* A possibly constant reference to a registry. +* An `entt::basic_view` with any possible combination of types. +* A possibly constant reference to any type `T` (that is, a context variable). + +The function type for free functions and decayed lambdas passed as parameters to +`emplace` is `void(const void *, entt::registry &)` instead. The first parameter +is an optional pointer to user defined data to provide upon registration: + +```cpp +clazz instance; +organizer.emplace(+[](const void *, entt::registry &) { /* ... */ }, &instance); +``` + +In all cases, it's also possible to associate a name with the task when creating +it. For example: + +```cpp +organizer.emplace<&free_function>("func"); +``` + +When a function of any type is registered with the organizer, everything it +accesses is considered a _resource_ (views are _unpacked_ and their types are +treated as resources). The _constness_ of the type also dictates its access mode +(RO/RW). In turn, this affects the resulting graph, since it influences the +possibility of launching tasks in parallel.
+As for the registry, if a function doesn't explicitly request it or requires a +constant reference to it, it's considered a read-only access. Otherwise, it's +considered as read-write access. All functions will still have the registry +among their resources. + +When registering a function, users can also require resources that aren't in the +list of parameters of the function itself. These are declared as template +parameters: + +```cpp +organizer.emplace<&free_function, position, velocity>("func"); +``` + +Similarly, users can override the access mode of a type again via template +parameters: + +```cpp +organizer.emplace<&free_function, const renderable>("func"); +``` + +In this case, even if `renderable` appears among the parameters of the function +as not constant, it will be treated as constant as regards the generation of the +task graph. + +To generate the task graph, the organizer offers the `graph` member function: + +```cpp +std::vector graph = organizer.graph(); +``` + +The graph is returned in the form of an adjacency list. Each vertex offers the +following features: + +* `ro_count` and `rw_count`: they return the number of resources accessed in + read-only or read-write mode. + +* `ro_dependency` and `rw_dependency`: useful for retrieving the type info + objects associated with the parameters of the underlying function. + +* `top_level`: indicates whether a node is a top level one, that is, it has no + entering edges. + +* `info`: returns the type info object associated with the underlying function. + +* `name`: returns the name associated with the given vertex if any, a null + pointer otherwise. + +* `callback`: a pointer to the function to execute and whose function type is + `void(const void *, entt::registry &)`. + +* `data`: optional data to provide to the callback. + +* `children`: the vertices reachable from the given node, in the form of indices + within the adjacency list. + +Since the creation of pools and resources within the registry isn't necessarily +thread safe, each vertex also offers a `prepare` function which can be called to +setup a registry for execution with the created graph: + +```cpp +auto graph = organizer.graph(); +entt::registry registry; + +for(auto &&node: graph) { + node.prepare(registry); +} +``` + +The actual scheduling of the tasks is the responsibility of the user, who can +use the preferred tool. + +## Context variables + +Each registry has a _context_ associated with it, which is an `any` object map +accessible by both type and _name_ for convenience. The _name_ isn't really a +name though. In fact, it's a numeric id of type `id_type` to be used as a key +for the variable. Any value is accepted, even runtime ones.
+The context is returned via the `ctx` functions and offers a minimal set of +feature including the following: + +```cpp +// creates a new context variable by type +registry.ctx().emplace(42, 'c'); + +// creates a new context variable with a name +registry.ctx().emplace_hint("my_variable"_hs, 42, 'c'); + +// gets the context variable by type as a non-const reference from a non-const registry +auto &var = registry.ctx().at(); + +// gets the context variable by name as a const reference from either a const or a non-const registry +const auto &cvar = registry.ctx().at("my_variable"_hs); + +// resets the context variable by type +registry.ctx().erase(); + +// resets the context variable associated with the given name +registry.ctx().erase("my_variable"_hs); +``` + +The type of a context variable must be such that it's default constructible and +can be moved. If the supplied type doesn't match that of the variable when using +a _name_, the operation will fail.
+For all users who want to use the context but don't want to create elements, the +`contains` and `find` functions are also available: + +```cpp +const bool contains = registry.ctx().contains(); +const my_type *value = registry.ctx().find("my_variable"_hs); +``` + +Also in this case, both functions support constant types and accept a _name_ for +the variable to look up, as does `at`. + +### Aliased properties + +Context variables can also be used to create aliases for existing variables that +aren't directly managed by the registry. In this case, it's also possible to +make them read-only.
+To do that, the type used upon construction must be a reference type and an +lvalue is necessarily provided as an argument: + +```cpp +time clock; +registry.ctx().emplace(clock); +``` + +Read-only aliased properties are created using const types instead: + +```cpp +registry.ctx().emplace(clock); +``` + +From the point of view of the user, there are no differences between a variable +that is managed by the registry and an aliased property. However, read-only +variables aren't accessible as non-const references: + +```cpp +// read-only variables only support const access +const my_type *ptr = registry.ctx().find(); +const my_type &var = registry.ctx().at(); +``` + +Aliased properties can be erased as it happens with any other variable. +Similarly, they can also be associated with user-generated _names_ (or ids). + +## Component traits + +In `EnTT`, almost everything is customizable. Components are no exception.
+In this case, the _standardized_ way to access all component properties is the +`component_traits` class. + +Various parts of the library access component properties through this class. It +makes it possible to use any type as a component, as long as its specialization +of `component_traits` implements all the required functionalities.
+The non-specialized version of this class contains the following members: + +* `in_place_delete`: `Type::in_place_delete` if present, false otherwise. +* `page_size`: `Type::page_size` if present, `ENTT_PACKED_PAGE` (for non-empty + types) or 0 (for empty types) otherwise. + +Where `Type` is any type of component. All properties can be customized by +specializing the above class and defining all its members, or by adding only +those of interest to a component definition: + +```cpp +struct transform { + static constexpr auto in_place_delete = true; + // ... other data members ... +}; +``` + +The `component_traits` class template will take care of correctly extracting the +properties from the supplied type to pass them to the rest of the library.
+In the case of a direct specialization, the class is also _sfinae-friendly_. It +supports single and multi type specializations as well as feature-based ones. + +## Pointer stability + +The ability to achieve pointer stability for one, several or all components is a +direct consequence of the design of `EnTT` and of its default storage.
+In fact, although it contains what is commonly referred to as a _packed array_, +the default storage is paged and doesn't suffer from invalidation of references +when it runs out of space and has to reallocate.
+However, this isn't enough to ensure pointer stability in case of deletion. For +this reason, a _stable_ deletion method is also offered. This one is such that +the position of the elements is preserved by creating tombstones upon deletion +rather than trying to fill the holes that are created. + +For performance reasons, `EnTT` favors storage compaction in all cases, although +often accessing a component occurs mostly randomly or traversing pools in a +non-linear order on the user side (as in the case of a hierarchy).
+In other words, pointer stability is not automatic but is enabled on request. + +### In-place delete + +The library offers out of the box support for in-place deletion, thus offering +storage with completely stable pointers. This is achieved by specializing the +`component_traits` class or by adding the required properties to the component +definition when needed.
+Views and groups adapt accordingly when they detect a storage with a different +deletion policy than the default. In particular: + +* Groups are incompatible with stable storage and will even refuse to compile. +* Multi type and runtime views are completely transparent to storage policies. +* Single type views for stable storage types offer the same interface of multi + type views. For example, only `size_hint` is available. + +In other words, the more generic version of a view is provided in case of stable +storage, even for a single type view.
+In no case a tombstone is returned from the view itself. Likewise, non-existent +components aren't returned, which could otherwise result in an UB. + +### Hierarchies and the like + +`EnTT` doesn't attempt in any way to offer built-in methods with hidden or +unclear costs to facilitate the creation of hierarchies.
+There are various solutions to the problem, such as using the following class: + +```cpp +struct relationship { + std::size_t children{}; + entt::entity first{entt::null}; + entt::entity prev{entt::null}; + entt::entity next{entt::null}; + entt::entity parent{entt::null}; + // ... other data members ... +}; +``` + +However, it should be pointed out that the possibility of having stable pointers +for one, many or all types solves the problem of hierarchies at the root in many +cases.
+In fact, if a certain type of component is visited mainly in random order or +according to hierarchical relationships, using direct pointers has many +advantages: + +```cpp +struct transform { + static constexpr auto in_place_delete = true; + + transform *parent; + // ... other data members ... +}; +``` + +Furthermore, it's quite common for a group of elements to be created close in +time and therefore fallback into adjacent positions, thus favoring locality even +on random accesses. Locality that won't be sacrificed over time given the +stability of storage positions, with undoubted performance advantages. + +## Meet the runtime + +`EnTT` takes advantage of what the language offers at compile-time. However, +this can have its downsides (well known to those familiar with type erasure +techniques).
+To fill the gap, the library also provides a bunch of utilities and feature that +can be very useful to handle types and pools at runtime. + +### A base class to rule them all + +Storage classes are fully self-contained types. These can be extended via mixins +to add more functionalities (generic or type specific). In addition, they offer +a basic set of functions that already allow users to go very far.
+The aim is to limit the need for customizations as much as possible, offering +what is usually necessary for the vast majority of cases. + +When a storage is used through its base class (i.e. when its actual type isn't +known), there is always the possibility of receiving a `type_info` describing +the type of the objects associated with the entities (if any): + +```cpp +if(entt::type_id() == base.type()) { + // ... +} +``` + +Furthermore, all features rely on internal functions that forward the calls to +the mixins. The latter can then make use of any information, which can be set +via `bind`: + +```cpp +base.bind(entt::forward_as_any(registry)); +``` + +The `bind` function accepts an `entt::any` object, that is a _typed type-erased_ +value.
+This is how a registry _passes_ itself to all pools that support signals and +also why a storage keeps sending events without requiring the registry to be +passed to it every time. + +Alongside these more specific things, there are also a couple of functions +designed to address some common requirements such as copying an entity.
+In particular, the base class behind a storage offers the possibility to _take_ +the object associated with an entity through an opaque pointer: + +```cpp +const void *instance = base.get(entity); +``` + +Similarly, the non-specialized `emplace` function accepts an optional opaque +pointer and behaves differently depending on the case: + +* When the pointer is null, the function tries to default-construct an instance + of the object to bind to the entity and returns true on success. + +* When the pointer is non-null, the function tries to copy-construct an instance + of the object to bind to the entity and returns true on success. + +This means that, starting from a reference to the base, it's possible to bind +components with entities without knowing their actual type and even initialize +them by copy if needed: + +```cpp +// create a copy of an entity component by component +for(auto &&curr: registry.storage()) { + if(auto &storage = curr.second; storage.contains(src)) { + storage.emplace(dst, storage.get(src)); + } +} +``` + +This is particularly useful to clone entities in an opaque way. In addition, the +decoupling of features allows for filtering or use of different copying policies +depending on the type. + +### Beam me up, registry + +`EnTT` is strongly based on types and has always allowed to create only one +storage of a certain type within a registry.
+However, this doesn't work well for users who want to create multiple storage of +the same type associated with different _names_, such as for interacting with a +scripting system. + +Nowadays, the library has solved this problem and offers the possibility of +associating a type with a _name_ (or rather, a numeric identifier): + +```cpp +using namespace entt::literals; +auto &&storage = registry.storage("second pool"_hs); +``` + +If a name isn't provided, the default storage associated with the given type is +always returned.
+Since the storage are also self-contained, the registry doesn't try in any way +to _duplicate_ its API and offer parallel functionalities for storage discovered +by name.
+However, there is still no limit to the possibilities of use. For example: + +```cpp +auto &&other = registry.storage("other"_hs); + +registry.emplace(entity); +storage.emplace(entity); +``` + +In other words, anything that can be done via the registry interface can also be +done directly on the reference storage.
+On the other hand, those calls involving all storage are guaranteed to also +_reach_ manually created ones: + +```cpp +// will remove the entity from both storage +registry.destroy(entity); +``` + +Finally, a storage of this type can be used with any view (which also accepts +multiple storages of the same type, if necessary): + +```cpp +// direct initialization +entt::basic_view direct{ + registry.storage(), + registry.storage("other"_hs) +}; + +// concatenation +auto join = registry.view() | entt::basic_view{registry.storage("other"_hs)}; +``` + +The possibility of direct use of storage combined with the freedom of being able +to create and use more than one of the same type opens the door to the use of +`EnTT` _at runtime_, which was previously quite limited.
+Sure the basic design remains very type-bound, but finally it's no longer bound +to this one option alone. + ## Snapshot: complete vs continuous The `registry` class offers basic support to serialization.
@@ -513,46 +1200,36 @@ suitable for local save/restore functionalities while the latter is suitable for creating client-server applications and for transferring somehow parts of the representation side to side. -To take a snapshot of the registry, use the `snapshot` member function. It -returns a temporary object properly initialized to _save_ the whole registry or -parts of it. - -Example of use: +To take a snapshot of a registry, use the `snapshot` class: ```cpp output_archive output; -registry.snapshot() +entt::snapshot{registry} .entities(output) - .destroyed(output) .component(output); ``` -It isn't necessary to invoke all these functions each and every time. What -functions to use in which case mostly depends on the goal and there is not a -golden rule to do that. +It isn't necessary to invoke all functions each and every time. What functions +to use in which case mostly depends on the goal and there is not a golden rule +for that. -The `entities` member function asks the registry to serialize all the entities -that are still in use along with their versions. On the other side, the -`destroyed` member function tells to the registry to serialize the entities that -have been destroyed and are no longer in use.
-These two functions can be used to save and restore the whole set of entities -with the versions they had during serialization. - -The `component` member function is a function template the aim of which is to -store aside components. The presence of a template parameter list is a -consequence of a couple of design choices from the past and in the present: +The `entities` member function makes the snapshot serialize all entities (both +those still alive and those released) along with their versions.
+On the other hand, the `component` member function is a function template the +aim of which is to store aside components. The presence of a template parameter +list is a consequence of a couple of design choices from the past and in the +present: * First of all, there is no reason to force a user to serialize all the components at once and most of the times it isn't desiderable. As an example, in case the stuff for the HUD in a game is put into the registry for some reasons, its components can be freely discarded during a serialization step - because probably the software already knows how to reconstruct the HUD - correctly from scratch. + because probably the software already knows how to reconstruct them correctly. * Furthermore, the registry makes heavy use of _type-erasure_ techniques - internally and doesn't know at any time what types of components it contains. - Therefore being explicit at the call point is mandatory. + internally and doesn't know at any time what component types it contains. + Therefore being explicit at the call site is mandatory. There exists also another version of the `component` member function that accepts a range of entities to serialize. This version is a bit slower than the @@ -565,7 +1242,7 @@ As an example: const auto view = registry.view(); output_archive output; -registry.snapshot().component(output, view.cbegin(), view.cend()); +entt::snapshot{registry}.component(output, view.begin(), view.end()); ``` Note that `component` stores items along with entities. It means that it works @@ -580,80 +1257,72 @@ The following sections describe both loaders and archives in details. 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.
-To do that, the registry offers a member function named `loader` that returns a -temporary object properly initialized to _restore_ a snapshot. - -Example of use: +To use it, just pass to the constructor a valid registry: ```cpp input_archive input; -registry.loader() +entt::snapshot_loader{registry} .entities(input) - .destroyed(input) .component(input) .orphans(); ``` -It isn't necessary to invoke all these functions each and every time. What -functions to use in which case mostly depends on the goal and there is not a -golden rule to do that. For obvious reasons, what is important is that the data -are restored in exactly the same order in which they were serialized. +It isn't necessary to invoke all functions each and every time. What functions +to use in which case mostly depends on the goal and there is not a golden rule +for that. For obvious reasons, what is important is that the data are restored +in exactly the same order in which they were serialized. -The `entities` and `destroyed` member functions restore the sets of entities and -the versions that the entities originally had at the source. +The `entities` member function restores the sets of entities and the versions +that they originally had at the source. The `component` member function restores all and only the components specified and assigns them to the right entities. Note that the template parameter list must be exactly the same used during the serialization. -The `orphans` member function literally destroys those entities that have no +The `orphans` member function literally releases those entities that have no components attached. It's usually useless if the snapshot is a full dump of the source. However, in case all the entities are serialized but only few components are saved, it could happen that some of the entities have no components once -restored. The best users can do to deal with them is to destroy those entities -and thus update their versions. +restored. The best the users can do to deal with them is to release those +entities and thus update their versions. ### Continuous loader 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.
+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. Because of that, this kind of loader offers a way to update automatically identifiers that are part of components (as an example, as data members or gathered in a container).
-Another difference with the snapshot loader is that the continuous loader does -not need to work with the private data structures of a registry. Furthermore, it -has an internal state that must persist over time. Therefore, there is no reason -to create it by means of a registry, or to limit its lifetime to that of a -temporary object. +Another difference with the snapshot loader is that the continuous loader has an +internal state that must persist over time. Therefore, there is no reason to +limit its lifetime to that of a temporary object. Example of use: ```cpp -entt::continuous_loader loader{registry}; +entt::continuous_loader loader{registry}; input_archive input; loader.entities(input) - .destroyed(input) .component(input, &dirty_component::parent, &dirty_component::child) .orphans() .shrink(); ``` -It isn't necessary to invoke all these functions each and every time. What -functions to use in which case mostly depends on the goal and there is not a -golden rule to do that. For obvious reasons, what is important is that the data -are restored in exactly the same order in which they were serialized. +It isn't necessary to invoke all functions each and every time. What functions +to use in which case mostly depends on the goal and there is not a golden rule +for that. For obvious reasons, what is important is that the data are restored +in exactly the same order in which they were serialized. -The `entities` and `destroyed` member functions restore groups of entities and -map each entity to a local counterpart when required. In other terms, for each -remote entity identifier not yet registered by the loader, the latter creates a -local identifier so that it can keep the local entity in sync with the remote -one. +The `entities` member function restores groups of entities and maps each entity +to a local counterpart when required. In other terms, for each remote entity +identifier not yet registered by the loader, it creates a local identifier so +that it can keep the local entity in sync with the remote one. The `component` member function restores all and only the components specified and assigns them to the right entities.
@@ -662,7 +1331,7 @@ In case the component contains entities itself (either as data members of type automatically. To do that, it's enough to specify the data members to update as shown in the example. -The `orphans` member function literally destroys those entities that have no +The `orphans` member function literally releases those entities that have no components after a restore. It has exactly the same purpose described in the previous section and works the same way. @@ -685,9 +1354,15 @@ In particular: void operator()(entt::entity); ``` - Where `entt::entity` is the type of the entities used by the registry. Note - that all the member functions of the snapshot class make also an initial call - to this endpoint to save the _size_ of the set they are going to store.
+ Where `entt::entity` is the type of the entities used by the registry.
+ Note that all member functions of the snapshot class make also an initial call + to store aside the _size_ of the set they are going to store. In this case, + the expected function type for the function call operator is: + + ```cpp + void operator()(std::underlying_type_t); + ``` + In addition, an archive must accept a pair of entity and component for each type to be serialized. Therefore, given a type `T`, the archive must contain a function call operator with the following signature: @@ -696,7 +1371,7 @@ In particular: void operator()(entt::entity, const T &); ``` - The output archive can freely decide how to serialize the data. The register + The output archive can freely decide how to serialize the data. The registry is not affected at all by the decision. * An input archive, the one used when restoring a snapshot, must expose a @@ -708,12 +1383,18 @@ In particular: Where `entt::entity` is the type of the entities used by the registry. Each time the function is invoked, the archive must read the next element from the - underlying storage and copy it in the given variable. Note that all the member - functions of a loader class make also an initial call to this endpoint to read - the _size_ of the set they are going to load.
- In addition, the archive must accept a pair of entity and component for each - type to be restored. Therefore, given a type `T`, the archive must contain a - function call operator with the following signature: + underlying storage and copy it in the given variable.
+ Note that all member functions of a loader class make also an initial call to + read the _size_ of the set they are going to load. In this case, the expected + function type for the function call operator is: + + ```cpp + void operator()(std::underlying_type_t &); + ``` + + In addition, the archive must accept a pair of references to an entity and its + component for each type to be restored. Therefore, given a type `T`, the + archive must contain a function call operator with the following signature: ```cpp void operator()(entt::entity &, T &); @@ -735,214 +1416,39 @@ the best way to do it. However, feel free to use it at your own risk. The basic idea is to store everything in a group of queues in memory, then bring everything back to the registry with different loaders. -## Prototype - -A prototype defines a type of an application in terms of its parts. They can be -used to assign components to entities of a registry at once.
-Roughly speaking, in most cases prototypes can be considered just as templates -to use to initialize entities according to _concepts_. In fact, users can create -how many prototypes they want, each one initialized differently from the others. - -The following is an example of use of a prototype: - -```cpp -entt::registry registry; -entt::prototype prototype{registry}; - -prototype.set(100.f, 100.f); -prototype.set(0.f, 0.f); - -// ... - -const auto entity = prototype(); -``` - -To assign and remove components from a prototype, it offers two dedicated member -functions named `set` and `unset`. The `has` member function can be used to know -if a given prototype contains one or more components and the `get` member -function can be used to retrieve the components. - -Creating an entity from a prototype is straightforward: - -* To create a new entity from scratch and assign it a prototype, this is the way - to go: - ```cpp - const auto entity = prototype(); - ``` - It is equivalent to the following invokation: - ```cpp - const auto entity = prototype.create(); - ``` - -* In case we want to initialize an already existing entity, we can provide the - `operator()` directly with the entity identifier: - ```cpp - prototype(entity); - ``` - It is equivalent to the following invokation: - ```cpp - prototype.assign(entity); - ``` - Note that existing components aren't overwritten in this case. Only those - components that the entity doesn't own yet are copied over. All the other - components remain unchanged. - -* Finally, to assign or replace all the components for an entity, thus - overwriting existing ones: - ```cpp - prototype.assign_or_replace(entity); - ``` - -In the examples above, the prototype uses its underlying registry to create -entities and components both for its purposes and when it's cloned. To use a -different repository to clone a prototype, all the member functions accept also -a reference to a valid registry as a first argument. - -Prototypes are a very useful tool that can save a lot of typing sometimes. -Furthermore, the codebase may be easier to maintain, since updating a prototype -is much less error prone than jumping around in the codebase to update all the -snippets copied and pasted around to initialize entities and components. - -## Helpers - -The so called _helpers_ are small classes and functions mainly designed to offer -built-in support for the most basic functionalities.
-The list of helpers will grow longer as time passes and new ideas come out. - -### Dependency function - -A _dependency function_ is a predefined listener, actually a function template -to use to automatically assign components to an entity when a type has a -dependency on some other types.
-The following adds components `a_type` and `another_type` whenever `my_type` is -assigned to an entity: - -```cpp -entt::connnect(registry.on_construct()); -``` - -A component is assigned to an entity and thus default initialized only in case -the entity itself hasn't it yet. It means that already existent components won't -be overriden.
-A dependency can easily be broken by means of the following function template: - -```cpp -entt::disconnect(registry.on_construct()); -``` - -### Tags - -There's nothing magical about the way tags can be assigned to entities while -avoiding a performance hit at runtime. Nonetheless, the syntax can be annoying -and that's why a more user-friendly shortcut is provided to do it.
-This shortcut is the alias template `entt::tag`. - -If used in combination with hashed strings, it helps to use tags where types -would be required otherwise. As an example: - -```cpp -registry.assign>(entity); -``` - -## Null entity - -In `EnTT`, there exists a sort of _null entity_ made available to users that is -accessible via the `entt::null` variable.
-The library guarantees that the following expression always returns false: - -```cpp -registry.valid(entt::null); -``` - -In other terms, a registry will reject the null entity in all cases because it -isn't considered valid. It means that the null entity cannot own components for -obvious reasons.
-The type of the null entity is internal and should not be used for any purpose -other than defining the null entity itself. However, there exist implicit -conversions from the null entity to identifiers of any allowed type: - -```cpp -entt::entity null = entt::null; -``` - -Similarly, the null entity can be compared to any other identifier: - -```cpp -const auto entity = registry.create(); -const bool null = (entity == entt::null); -``` - -## Context variables - -It is often convenient to assign context variables to a registry, so as to make -it the only _source of truth_ of an application.
-This is possible by means of a member function named `set` to use to create a -context variable from a given type. Later on, either `ctx` or `try_ctx` can be -used to retrieve the newly created instance and `unset` is there to literally -reset it if needed. - -Example of use: - -```cpp -// creates a new context variable initialized with the given values -registry.set(42, 'c'); - -// gets the context variable -const auto &var = registry.ctx(); - -// if in doubts, probe the registry to avoid assertions in case of errors -if(auto *ptr = registry.try_ctx(); ptr) { - // uses the context variable associated with the registry, if any -} - -// unsets the context variable -registry.unset(); -``` - -The type of a context variable must be such that it's default constructible and -can be moved. The `set` member function either creates a new instance of the -context variable or overwrites an already existing one if any. The `try_ctx` -member function returns a pointer to the context variable if it exists, -otherwise it returns a null pointer. This fits well with the `if` statement with -initializer. - # Views and Groups -First of all, it is worth answering an obvious question: why views and -groups?
-Briefly, they are a good tool to enforce single responsibility. A system that -has access to a registry can create and destroy entities, as well as assign and +First of all, it's worth answering a question: why views and groups?
+Briefly, they're a good tool to enforce single responsibility. A system that has +access to a registry can create and destroy entities, as well as assign and remove components. On the other side, a system that has access to a view or a -group can only iterate entities and their components, then read or update the -data members of the latter.
+group can only iterate, read and update entities and components.
It is a subtle difference that can help designing a better software sometimes. -More in details, views are a non-intrusive tool to access entities and -components without affecting other functionalities or increasing the memory -consumption. On the other side, groups are an intrusive tool that allows to -reach higher performance along critical paths but has also a price to pay for -that. +More in details: + +* Views are a non-intrusive tool to access entities and components without + affecting other functionalities or increasing the memory consumption. + +* Groups are an intrusive tool that allows to reach higher performance along + critical paths but has also a price to pay for that. There are mainly two kinds of views: _compile-time_ (also known as `view`) and runtime (also known as `runtime_view`).
-The former require that users indicate at compile-time what are the components -involved and can make several optimizations because of that. The latter can be -constructed at runtime instead and are a bit slower to iterate entities and -components.
-In both cases, creating and destroying a view isn't expensive at all because -views don't have any type of initialization. Moreover, views don't affect any -other functionality of the registry and keep memory usage at a minimum. +The former requires a compile-time list of component types and can make several +optimizations because of that. The latter can be constructed at runtime instead +using numerical type identifiers and are a bit slower to iterate.
+In both cases, creating and destroying a view isn't expensive at all since they +don't have any type of initialization. Groups come in three different flavors: _full-owning groups_, _partial-owning groups_ and _non-owning groups_. The main difference between them is in terms of performance.
-Groups can literally _own_ one or more types of components. It means that they -will be allowed to rearrange pools so as to speed up iterations. Roughly -speaking: the more components a group owns, the faster it is to iterate them. -On the other side, a given component can belong only to one group, so users have -to define groups carefully to get the best out of them. - -Continue reading for more details or refer to the inline documentation. +Groups can literally _own_ one or more component types. They are allowed to +rearrange pools so as to speed up iterations. Roughly speaking: the more +components a group owns, the faster it is to iterate them.
+A given component can belong to multiple groups only if they are _nested_, so +users have to define groups carefully to get the best out of them. ## Views @@ -950,139 +1456,152 @@ A view behaves differently if it's constructed for a single component or if it has been created to iterate multiple components. Even the API is slightly different in the two cases. -Single component views are specialized in order to give a boost in terms of -performance in all the situations. This kind of views can access the underlying -data structures directly and avoid superfluous checks. There is nothing as fast -as a single component view. In fact, they walk through a packed array of -components and return them one at a time.
-Single component views offer a bunch of functionalities to get the number of -entities they are going to return and a raw access to the entity list as well as -to the component list. It's also possible to ask a view if it contains a -given entity.
+Single type views are specialized to give a boost in terms of performance in all +the situations. This kind of views can access the underlying data structures +directly and avoid superfluous checks. There is nothing as fast as a single type +view. In fact, they walk through a packed (actually paged) array of components +and return them one at a time.
+Views also offer a bunch of functionalities to get the number of entities and +components they are going to return. It's also possible to ask a view if it +contains a given entity.
Refer to the inline documentation for all the details. -Multi component views iterate entities that have at least all the given -components in their bags. During construction, these views look at the number of -entities available for each component and pick up a reference to the smallest -set of candidates in order to speed up iterations.
-They offer fewer functionalities than their companion views for single -component. In particular, a multi component view exposes utility functions to -get the estimated number of entities it is going to return and to know whether -it's empty or not. It's also possible to ask a view if it contains a given -entity.
+Multi type views iterate entities that have at least all the given components in +their bags. During construction, these views look at the number of entities +available for each component and pick up a reference to the smallest set of +candidates in order to speed up iterations.
+They offer fewer functionalities than single type views. In particular, a multi +type view exposes utility functions to get the estimated number of entities it +is going to return and to know if it contains a given entity.
Refer to the inline documentation for all the details. -There is no need to store views around for they are extremely cheap to -construct, even though they can be copied without problems and reused freely. -Views also return newly created and correctly initialized iterators whenever -`begin` or `end` are invoked. +There is no need to store views aside as they are extremely cheap to construct. +In fact, this is even discouraged when creating a view from a const registry. +Since all storage are lazily initialized, they may not exist when the view is +built. Therefore, the view itself will refer to an empty _placeholder_ and will +never be re-assigned the actual storage.
+In all cases, views return newly created and correctly initialized iterators for +the storage they refer to when `begin` or `end` are invoked. Views share the way they are created by means of a registry: ```cpp -// single component view +// single type view auto single = registry.view(); -// multi component view +// multi type view auto multi = registry.view(); ``` +Filtering entities by components is also supported: + +```cpp +auto view = registry.view(entt::exclude); +``` + To iterate a view, either use it in a range-for loop: ```cpp -auto view = registry.view(); +auto view = registry.view(); for(auto entity: view) { // a component at a time ... auto &position = view.get(entity); auto &velocity = view.get(entity); - // ... or multiple components at once - auto &[pos, vel] = view.get(entity); + // ... multiple components ... + auto [pos, vel] = view.get(entity); + + // ... all components at once + auto [pos, vel, rend] = view.get(entity); // ... } ``` -Or rely on the `each` member function to iterate entities and get all their -components at once: +Or rely on the `each` member functions to iterate both entities and components +at once: ```cpp +// through a callback registry.view().each([](auto entity, auto &pos, auto &vel) { // ... }); + +// using an input iterator +for(auto &&[entity, pos, vel]: registry.view().each()) { + // ... +} ``` -The `each` member function is highly optimized. Unless users want to iterate -only entities or get only some of the components, this should be the preferred -approach. Note that the entity can also be excluded from the parameter list if -not required, but this won't improve performance for multi component views.
-There exists also an alternative version of `each` named `less` that works -exactly as its counterpart but for the fact that it doesn't return empty -components to the caller. +Note that entities can also be excluded from the parameter list when received +through a callback and this can improve even further the performance during +iterations.
+Since they aren't explicitly instantiated, empty components aren't returned in +any case. -As a side note, when using a single component view, the most common error is to -invoke `get` with the type of the component as a template parameter. This is -probably due to the fact that it's required for multi component views: +As a side note, in the case of single type views, `get` accepts but doesn't +strictly require a template parameter, since the type is implicitly defined. +However, when the type isn't specified, for consistency with the multi type +view, the instance will be returned using a tuple: ```cpp -auto view = registry.view(); +auto view = registry.view(); for(auto entity: view) { - const auto &vel = view.get(entity); + auto [renderable] = view.get(entity); // ... } ``` -However, in case of a single component view, `get` doesn't accept a template -parameter, since it's implicitly defined by the view itself: +**Note**: prefer the `get` member function of a view instead of that of a +registry during iterations to get the types iterated by the view itself. + +### View pack + +Views are combined with each other to create new and more specific types.
+The type returned when combining multiple views together is itself a view, more +in general a multi component one. + +Combining different views tries to mimic C++20 ranges: ```cpp -auto view = registry.view(); +auto view = registry.view(); +auto other = registry.view(); -for(auto entity: view) { - const auto &renderable = view.get(entity); - // ... -} +auto pack = view | other; ``` -**Note**: prefer the `get` member function of a view instead of the `get` member -function template of a registry during iterations, if possible. However, keep in -mind that it works only with the components of the view itself. +The constness of the types is preserved and their order depends on the order in +which the views are combined. Therefore, the pack in the example above will +return an instance of `position` first and then one of `velocity`.
+Since combining views generates views, a chain can be of arbitrary length and +the above type order rules apply sequentially. -## Runtime views +### Runtime views Runtime views iterate entities that have at least all the given components in their bags. During construction, these views look at the number of entities -available for each component and pick up a reference to the smallest -set of candidates in order to speed up iterations.
-They offer more or less the same functionalities of a multi component view. -However, they don't expose a `get` member function and users should refer to the -registry that generated the view to access components. In particular, a runtime -view exposes utility functions to get the estimated number of entities it is -going to return and to know whether it's empty or not. It's also possible to ask -a runtime view if it contains a given entity.
+available for each component and pick up a reference to the smallest set of +candidates in order to speed up iterations.
+They offer more or less the same functionalities of a multi type view. However, +they don't expose a `get` member function and users should refer to the registry +that generated the view to access components. In particular, a runtime view +exposes utility functions to get the estimated number of entities it is going to +return and to know whether it's empty or not. It's also possible to ask a +runtime view if it contains a given entity.
Refer to the inline documentation for all the details. -Runtime view are extremely cheap to construct and should not be stored around in +Runtime views are pretty cheap to construct and should not be stored aside in any case. They should be used immediately after creation and then they should be -thrown away. The reasons for this go far beyond the scope of this document.
+thrown away.
To iterate a runtime view, either use it in a range-for loop: ```cpp -using component_type = typename decltype(registry)::component_type; -component_type types[] = { registry.type(), registry.type() }; - -auto view = registry.runtime_view(std::cbegin(types), std::cend(types)); +entt::runtime_view view{}; +view.iterate(registry.storage()).iterate(registry.storage()); for(auto entity: view) { - // a component at a time ... - auto &position = registry.get(entity); - auto &velocity = registry.get(entity); - - // ... or multiple components at once - auto &[pos, vel] = registry.get(entity); - // ... } ``` @@ -1090,27 +1609,30 @@ for(auto entity: view) { Or rely on the `each` member function to iterate entities: ```cpp -using component_type = typename decltype(registry)::component_type; -component_type types[] = { registry.type(), registry.type() }; - -auto view = registry.runtime_view(std::cbegin(types), std::cend(types)).each([](auto entity) { - // ... -}); +entt::runtime_view{} + .iterate(registry.storage()) + .iterate(registry.storage()) + .each([](auto entity) { + // ... + }); ``` -Performance are exactly the same in both cases. +Performance are exactly the same in both cases.
+Filtering entities by components is also supported for this kind of views: -**Note**: runtime views are meant for all those cases where users don't know at -compile-time what components to use to iterate entities. This is particularly -well suited to plugin systems and mods in general. Where possible, don't use -runtime views, as their performance are slightly inferior to those of the other -views. +```cpp +entt::runtime_view view{}; +view.iterate(registry.storage()).exclude(registry.storage()); +``` + +Runtime views are meant for when users don't know at compile-time what types to +_use_ to iterate entities. The `storage` member function of a registry could be +useful in this regard. ## Groups -Groups are meant to iterate multiple components at once and offer a faster -alternative to views. Roughly speaking, they just play in another league when -compared to views.
+Groups are meant to iterate multiple components at once and to offer a faster +alternative to multi type views.
Groups overcome the performance of the other tools available but require to get the ownership of components and this sets some constraints on pools. On the other side, groups aren't an automatism that increases memory consumption, @@ -1134,16 +1656,15 @@ This is due to the fact that they must _observe_ changes in the pools of interest and arrange data _correctly_ when needed for the types they own.
That being said, the way groups operate is beyond the scope of this document. However, it's unlikely that users will be able to appreciate the impact of -groups on other functionalities of the registry. +groups on the other functionalities of a registry. -Groups offer a bunch of functionalities to get the number of entities they are -going to return and a raw access to the entity list as well as to the component -list for owned components. It's also possible to ask a group if it contains a -given entity.
+Groups offer a bunch of functionalities to get the number of entities and +components they are going to return. It's also possible to ask a group if it +contains a given entity.
Refer to the inline documentation for all the details. -There is no need to store groups around for they are extremely cheap to -construct, even though they can be copied without problems and reused freely. +There is no need to store groups aside for they are extremely cheap to create, +even though valid groups can be copied without problems and reused freely.
A group performs an initialization step the very first time it's requested and this could be quite costly. To avoid it, consider creating the group when no components have been assigned yet. If the registry is empty, preparation is @@ -1153,41 +1674,46 @@ iterators whenever `begin` or `end` are invoked. To iterate groups, either use them in a range-for loop: ```cpp -auto group = registry.group(entt::get); +auto group = registry.group(entt::get); for(auto entity: group) { // a component at a time ... auto &position = group.get(entity); auto &velocity = group.get(entity); - // ... or multiple components at once - auto &[pos, vel] = group.get(entity); + // ... multiple components ... + auto [pos, vel] = group.get(entity); + + // ... all components at once + auto [pos, vel, rend] = group.get(entity); // ... } ``` -Or rely on the `each` member function to iterate entities and get all their -components at once: +Or rely on the `each` member functions to iterate both entities and components +at once: ```cpp +// through a callback registry.group(entt::get).each([](auto entity, auto &pos, auto &vel) { // ... }); -``` -The `each` member function is highly optimized. Unless users want to iterate -only entities, this should be the preferred approach. Note that the entity can -also be excluded from the parameter list if not required and it can improve even -further the performance during iterations. +// using an input iterator +for(auto &&[entity, pos, vel]: registry.group(entt::get).each()) { + // ... +} +``` -**Note**: prefer the `get` member function of a group instead of the `get` -member function template of a registry during iterations, if possible. However, -keep in mind that it works only with the components of the group itself. +Note that entities can also be excluded from the parameter list when received +through a callback and this can improve even further the performance during +iterations.
+Since they aren't explicitly instantiated, empty components aren't returned in +any case. -Let's go a bit deeper into the different types of groups made available by this -library to know how they are constructed and what are the differences between -them. +**Note**: prefer the `get` member function of a group instead of that of a +registry during iterations to get the types iterated by the group itself. ### Full-owning groups @@ -1195,7 +1721,7 @@ A full-owning group is the fastest tool an user can expect to use to iterate multiple components at once. It iterates all the components directly, no indirection required. This type of groups performs more or less as if users are accessing sequentially a bunch of packed arrays of components all sorted -identically. +identically, with no jumps nor branches. A full-owning group is created as: @@ -1210,14 +1736,11 @@ auto group = registry.group(entt::exclude); ``` Once created, the group gets the ownership of all the components specified in -the template parameter list and arranges their pools so as to iterate all of -them as fast as possible. +the template parameter list and arranges their pools as needed. Sorting owned components is no longer allowed once the group has been created. However, full-owning groups can be sorted by means of their `sort` member -functions, if required. Sorting a full-owning group affects all the instance of -the same group (it means that users don't have to call `sort` on each instance -to sort all of them because they share the underlying data structure). +functions. Sorting a full-owning group affects all its instances. ### Partial-owning groups @@ -1240,15 +1763,12 @@ auto group = registry.group(entt::get, entt::exclude(entt::get, entt::exclude()`. +* `registry.group()`. + +However, the same type can be owned by groups belonging to the same _family_, +also called _nested groups_, such as: + +* `registry.group()`. +* `registry.group()`. + +Fortunately, these are also very common cases if not the most common ones.
+It allows to increase performance on a greater number of component combinations. + +Two nested groups are such that they own at least one component type and the list +of component types involved by one of them is contained entirely in that of the +other. More specifically, this applies independently to all component lists used +to define a group.
+Therefore, the rules for defining whether two or more groups are nested can be +summarized as: + +* One of the groups involves one or more additional component types with respect + to the other, whether they are owned, observed or excluded. + +* The list of component types owned by the most restrictive group is the same or + contains entirely that of the others. This also applies to the list of + observed and excluded components. + +It means that nested groups _extend_ their parents by adding more conditions in +the form of new components. + +As mentioned, the components don't necessarily have to be all _owned_ so that +two groups can be considered nested. The following definitions are fully valid: + +* `registry.group(entt::get)`. +* `registry.group(entt::get)`. +* `registry.group(entt::get)`. + +Exclusion lists also play their part in this respect. When it comes to defining +nested groups, an excluded component type `T` is treated as being an observed +type `not_T`. Therefore, consider these two definitions: + +* `registry.group()`. +* `registry.group(entt::exclude)`. + +They are treated as if users were defining the following groups: + +* `group()`. +* `group(entt::get)`. + +Where `not_rotation` is an empty tag present only when `rotation` is not. + +Because of this, to define a new group that is more restrictive than an existing +one, it's enough to take the list of component types of the latter and extend it +by adding new component types either owned, observed or excluded, without any +precautions depending on the case.
+The opposite is also true. To define a _larger_ group, it will be enough to take +an existing one and remove _constraints_ from it, in whatever form they are +expressed.
+Note that the greater the number of component types involved by a group, the +more restrictive it is. + +Despite the extreme flexibility of nested groups which allow to independently +use component types either owned, observed or excluded, the real strength of +this tool lies in the possibility of defining a greater number of groups that +**own** the same components, thus offering the best performance in more +cases.
+In fact, given a list of component types involved by a group, the greater the +number of those owned, the greater the performance of the group itself. + +As a side note, it's no longer possible to sort all groups when defining nested +ones. This is because the most restrictive group shares its elements with the +less restrictive ones and ordering the latter would invalidate the former.
+However, given a family of nested groups, it's still possible to sort the most +restrictive of them. To prevent users from having to remember which of their +groups is the most restrictive, the registry class offers the `sortable` member +function to know if a group can be sorted or not. + +## Types: const, non-const and all in between The `registry` class offers two overloads when it comes to constructing views -and groups: a const version and a non-const one. The former accepts both const -and non-const types as template parameters, the latter accepts only const types +and groups: a const version and a non-const one. The former accepts only const +types as template parameters, the latter accepts both const and non-const types instead.
-It means that views and groups can be constructed also from a const registry and -they propagate the constness of the registry to the types involved. As an -example: +It means that views and groups can be constructed from a const registry and they +propagate the constness of the registry to the types involved. As an example: ```cpp entt::view view = std::as_const(registry).view(); @@ -1302,7 +1898,7 @@ entt::view view = registry.view -In other terms, these statements are all valid: +Similarly, these statements are all valid: ```cpp position &pos = view.get(entity); @@ -1321,8 +1917,7 @@ std::tuple tup = view.get(entity); std::tuple ctup = view.get(entity); ``` -Similarly, the `each` member functions will propagate constness to the type of -the components returned during iterations: +The `each` member functions also propagates constness to its _return values_: ```cpp view.each([](auto entity, position &pos, const velocity &vel) { @@ -1330,9 +1925,8 @@ view.each([](auto entity, position &pos, const velocity &vel) { }); ``` -Obviously, a caller can still refer to the `position` components through a const -reference because of the rules of the language that fortunately already allow -it. +A caller can still refer to the `position` components through a const reference +because of the rules of the language that fortunately already allow it. The same concepts apply to groups as well. @@ -1350,78 +1944,84 @@ registry.each([](auto entity) { }); ``` -It returns to the caller all the entities that are still in use by means of the -given function.
As a rule of thumb, consider using a view or a group if the goal is to iterate entities that have a determinate set of components. These tools are usually much -faster than combining this function with a bunch of custom tests.
-In all the other cases, this is the way to go. - -There exists also another member function to use to retrieve orphans. An orphan -is an entity that is still in use and has no assigned components.
-The signature of the function is the same of `each`: +faster than combining the `each` function with a bunch of custom tests.
+In all the other cases, this is the way to go. For example, it's possible to +combine `each` with the `orphan` member function to clean up orphan entities +(that is, entities that are still in use and have no assigned components): ```cpp -registry.orphans([](auto entity) { - // ... +registry.each([®istry](auto entity) { + if(registry.orphan(entity)) { + registry.release(entity); + } }); ``` -To test the _orphanity_ of a single entity, use the member function `orphan` -instead. It accepts a valid entity identifer as an argument and returns true in -case the entity is an orphan, false otherwise. - -In general, all these functions can result in poor performance.
-`each` is fairly slow because of some checks it performs on each and every -entity. For similar reasons, `orphans` can be even slower. Both functions should -not be used frequently to avoid the risk of a performance hit. +In general, iterating all entities can result in poor performance. It should not +be done frequently to avoid the risk of a performance hit.
+However, it can be convenient when initializing an editor or to reclaim pending +identifiers. ## What is allowed and what is not Most of the _ECS_ available out there don't allow to create and destroy entities -and components during iterations.
+and components during iterations, nor to have pointer stability.
`EnTT` partially solves the problem with a few limitations: -* Creating entities and components is allowed during iterations in almost all - cases. +* Creating entities and components is allowed during iterations in most cases + and it never invalidates already existing references. + * Deleting the current entity or removing its components is allowed during - iterations. For all the other entities, destroying them or removing their - components isn't allowed and can result in undefined behavior. + iterations but it could invalidate references. For all the other entities, + destroying them or removing their iterated components isn't allowed and can + result in undefined behavior. + +* When pointer stability is enabled for the type leading the iteration, adding + instances of the same type may or may not cause the entity involved to be + returned. Destroying entities and components is always allowed instead, even + if not currently iterated, without the risk of invalidating any references. -In these cases, iterators aren't invalidated. To be clear, it doesn't mean that -also references will continue to be valid.
-Consider the following example: +In other terms, iterators are rarely invalidated. Also, component references +aren't invalidated when a new element is added while they could be invalidated +upon destruction due to the _swap-and-pop_ policy, unless the type leading the +iteration undergoes in-place deletion.
+As an example, consider the following snippet: ```cpp registry.view([&](const auto entity, auto &pos) { - registry.assign(registry.create(), 0., 0.); - pos.x = 0.; // warning: dangling pointer + registry.emplace(registry.create(), 0., 0.); + // references remain stable after adding new instances + pos.x = 0.; }); ``` -The `each` member function won't break (because iterators aren't invalidated) -but there are no guarantees on references. Use a common range-for loop and get -components directly from the view or move the creation of components at the end -of the function to avoid dangling pointers. +The `each` member function won't break (because iterators remain valid) nor will +any reference be invalidated. Instead, more attention should be paid to the +destruction of entities or the removal of components.
+Use a common range-for loop and get components directly from the view or move +the deletion of entities and components at the end of the function to avoid +dangling pointers. -Iterators are invalidated instead and the behavior is undefined if an entity is -modified or destroyed and it's not the one currently returned by the iterator -nor a newly created one.
+For all types that don't offer stable pointers, iterators are also invalidated +and the behavior is undefined if an entity is modified or destroyed and it's not +the one currently returned by the iterator nor a newly created one.
To work around it, possible approaches are: * Store aside the entities and the components to be removed and perform the operations at the end of the iteration. + * Mark entities and components with a proper tag component that indicates they must be purged, then perform a second iteration to clean them up one by one. A notable side effect of this feature is that the number of required allocations -is further reduced in most of the cases. +is further reduced in most cases. ### More performance, more constraints -Groups are a (much) faster alternative to views. However, the higher the -performance, the greater the constraints on what is allowed and what is -not.
+Groups are a faster alternative to views. However, the higher the performance, +the greater the constraints on what is allowed and what is not.
In particular, groups add in some rare cases a limitation on the creation of components during iterations. It happens in quite particular cases. Given the nature and the scope of the groups, it isn't something in which it will happen @@ -1439,50 +2039,48 @@ limitations to the destruction of components and entities.
Fortunately, this isn't always true. In fact, it almost never is and this happens only under certain conditions. In particular: -* Iterating a type of component that is part of a group with a single component - view and adding to an entity all the components required to get it into the - group may invalidate the iterators. +* Iterating a type of component that is part of a group with a single type view + and adding to an entity all the components required to get it into the group + may invalidate the iterators. -* Iterating a type of component that is part of a group with a multi component - view and adding to an entity all the components required to get it into the - group can invalidate the iterators, unless users specify another type of - component to use to induce the order of iteration of the view (in this case, - the former is treated as a free type and isn't affected by the limitation). +* Iterating a type of component that is part of a group with a multi type view + and adding to an entity all the components required to get it into the group + can invalidate the iterators, unless users specify another type of component + to use to induce the order of iteration of the view (in this case, the former + is treated as a free type and isn't affected by the limitation). In other words, the limitation doesn't exist as long as a type is treated as a -free type (as an example with multi component views and partial- or non-owning +free type (as an example with multi type views and partial- or non-owning groups) or iterated with its own group, but it can occur if the type is used as a main type to rule on an iteration.
This happens because groups own the pools of their components and organize the data internally to maximize performance. Because of that, full consistency for owned components is guaranteed only when they are iterated as part of their -groups or as free types with multi component views and groups in general. +groups or as free types with multi type views and groups in general. # Empty type optimization -An empty type `T` is such that `std::is_empty_v` returns true. They are also -the same types for which _empty base optimization_ (EBO) is possibile.
+An empty type `T` is such that `std::is_empty_v` returns true. They also are +the same types for which _empty base optimization_ (EBO) is possible.
`EnTT` handles these types in a special way, optimizing both in terms of performance and memory usage. However, this also has consequences that are worth mentioning. -When an empty type is detected, it's not instantiated in any case. Therefore, -only the entities to which it's assigned are made available. All the iterators -as well as the `get` member functions of the registry, the views and the groups -will return temporary objects. Similarly, some functions such as `try_get` or -the raw access to the list of components aren't available for this kind of -types.
+When an empty type is detected, it's not instantiated by default. Therefore, +only the entities to which it's assigned are made available. There doesn't exist +a way to _get_ empty types from a registry. Views and groups will never return +their instances (for example, during a call to `each`).
On the other hand, iterations are faster because only the entities to which the -type is assigned are considered. Moreover, less memory is used, since there -doesn't exist any instance of the component, no matter how many entities it is -assigned to. - -For similar reasons, wherever a function type of a listener accepts a component, -it cannot be caught by a non-const reference. Capture it by copy or by const -reference instead. +type is assigned are considered. Moreover, less memory is used, mainly because +there doesn't exist any instance of the component, no matter how many entities +it is assigned to. -More in general, none of the features offered by the library is affected, but -for the ones that require to return actual instances. +More in general, none of the feature offered by the library is affected, but for +the ones that require to return actual instances.
+This optimization is disabled by defining the `ENTT_NO_ETO` macro. In this case, +empty types are treated like all other types. Setting a page size at component +level via the `component_traits` class template is another way to disable this +optimization selectively rather than globally. # Multithreading @@ -1518,11 +2116,19 @@ are completely responsible for synchronization whether required. On the other hand, they could get away with it without having to resort to particular expedients. +Finally, `EnTT` can be configured via a few compile-time definitions to make +some of its parts implicitly thread-safe, roughly speaking only the ones that +really make sense and can't be turned around.
+In particular, when multiple instances of objects referencing the type index +generator (such as the `registry` class) are used in different threads, then it +might be useful to define `ENTT_USE_ATOMIC`.
+See the relevant documentation for more information. + ## Iterators -A special mention is needed for the iterators returned by the views and the -groups. Most of the time they meet the requirements of random access iterators, -in all cases they meet at least the requirements of forward iterators.
+A special mention is needed for the iterators returned by views and groups. Most +of the times they meet the requirements of random access iterators, in all cases +they meet at least the requirements of forward iterators.
In other terms, they are suitable for use with the parallel algorithms of the standard library. If it's not clear, this is a great thing. @@ -1544,9 +2150,49 @@ knows what artifacts that are difficult to maintain over time. Unfortunately, because of the limitations of the current revision of the standard, the parallel `std::for_each` accepts only forward iterators. This -means that the iterators provided by the library cannot return proxy objects as -references and **must** return actual reference types instead.
+means that the default iterators provided by the library cannot return proxy +objects as references and **must** return actual reference types instead.
This may change in the future and the iterators will almost certainly return -both the entities and a list of references to their components sooner or later. -Multi-pass guarantee won't break in any case and the performance should even -benefit from it further. +both the entities and a list of references to their components by default sooner +or later. Multi-pass guarantee won't break in any case and the performance +should even benefit from it further. + +## Const registry + +A const registry is also fully thread safe. This means that it won't be able to +lazily initialize a missing storage when a view is generated.
+The reason for this is easy to explain. To avoid requiring types to be +_announced_ in advance, a registry lazily creates the storage objects for the +different components. However, this isn't possible for a thread safe const +registry.
+On the other side, all pools must necessarily _exist_ when creating a view. +Therefore, static _placeholders_ for missing storage are used to fill the gap. + +Note that returned views are always valid and behave as expected in the context +of the caller. The only difference is that static _placeholders_ (if any) are +never renewed.
+As a result, a view created from a const registry may behave incorrectly over +time if it's kept for a second use.
+Therefore, if the general advice is to create views when necessary and discard +them immediately afterwards, this becomes almost a rule when it comes to views +generated from a const registry. + +Fortunately, there is also a way to instantiate storage classes early when in +doubt or when there are special requirements.
+Calling the `storage` method is equivalent to _announcing_ the existence of a +particular storage, to avoid running into problems. For those interested, there +are also alternative approaches, such as a single threaded tick for the registry +warm-up, but these are not always applicable.
+In this case, no placeholders will be used since all storage exist. In other +words, views never risk becoming _invalid_. + +# Beyond this document + +There are many other features and functions not listed in this document.
+`EnTT` and in particular its ECS part is in continuous development and some +things could be forgotten, others could have been omitted on purpose to reduce +the size of this file. Unfortunately, some parts may even be outdated and still +to be updated. + +For further information, it's recommended to refer to the documentation included +in the code itself or join the official channels to ask a question. diff --git a/modules/entt/docs/md/faq.md b/modules/entt/docs/md/faq.md index acc51ca..feea06d 100644 --- a/modules/entt/docs/md/faq.md +++ b/modules/entt/docs/md/faq.md @@ -8,6 +8,12 @@ * [Introduction](#introduction) * [FAQ](#faq) * [Why is my debug build on Windows so slow?](#why-is-my-debug-build-on-windows-so-slow) + * [How can I represent hierarchies with my components?](#how-can-i-represent-hierarchies-with-my-components) + * [Custom entity identifiers: yay or nay?](#custom-entity-identifiers-yay-or-nay) + * [Warning C4307: integral constant overflow](#warning-C4307-integral-constant-overflow) + * [Warning C4003: the min, the max and the macro](#warning-C4003-the-min-the-max-and-the-macro) + * [The standard and the non-copyable types](#the-standard-and-the-non-copyable-types) + * [Which functions trigger which signals](#which-functions-trigger-which-signals) @@ -45,11 +51,161 @@ First of all, there are two things to do in a Windows project: * Set the [`_ITERATOR_DEBUG_LEVEL`](https://docs.microsoft.com/cpp/standard-library/iterator-debug-level) macro to 0. This will disable checked iterators and iterator debugging. -Moreover, the macro `ENTT_DISABLE_ASSERT` should be defined to disable internal -checks made by `EnTT` in debug. These are asserts introduced to help the users, -but require to access to the underlying containers and therefore risk ruining -the performance in some cases. +Moreover, the macro `ENTT_ASSERT` should be redefined to disable internal checks +made by `EnTT` in debug: + +```cpp +#define ENTT_ASSERT(...) ((void)0) +``` + +These asserts are introduced to help the users but they require to access to the +underlying containers and therefore risk ruining the performance in some cases. With these changes, debug performance should increase enough for most cases. If you want something more, you can can also switch to an optimization level `O0` or preferably `O1`. + +## How can I represent hierarchies with my components? + +This is one of the first questions that anyone makes when starting to work with +the entity-component-system architectural pattern.
+There are several approaches to the problem and what’s the best one depends +mainly on the real problem one is facing. In all cases, how to do it doesn't +strictly depend on the library in use, but the latter can certainly allow or +not different techniques depending on how the data are laid out. + +I tried to describe some of the techniques that fit well with the model of +`EnTT`. [Here](https://skypjack.github.io/2019-06-25-ecs-baf-part-4/) is the +first post of a series that tries to explore the problem. More will probably +come in future.
+In addition, `EnTT` also offers the possibility to create stable storage types +and therefore have pointer stability for one, all or some components. This is by +far the most convenient solution when it comes to creating hierarchies and +whatnot. See the documentation for the ECS part of the library and in particular +what concerns the `component_traits` class for further details. + +## Custom entity identifiers: yay or nay? + +Custom entity identifiers are definitely a good idea in two cases at least: + +* If `std::uint32_t` isn't large enough for your purposes, since this is the + underlying type of `entt::entity`. +* If you want to avoid conflicts when using multiple registries. + +Identifiers can be defined through enum classes and class types that define an +`entity_type` member of type `std::uint32_t` or `std::uint64_t`.
+In fact, this is a definition equivalent to that of `entt::entity`: + +```cpp +enum class entity: std::uint32_t {}; +``` + +There is no limit to the number of identifiers that can be defined. + +## Warning C4307: integral constant overflow + +According to [this](https://github.com/skypjack/entt/issues/121) issue, using a +hashed string under VS could generate a warning.
+First of all, I want to reassure you: it's expected and harmless. However, it +can be annoying. + +To suppress it and if you don't want to suppress all the other warnings as well, +here is a workaround in the form of a macro: + +```cpp +#if defined(_MSC_VER) +#define HS(str) __pragma(warning(suppress:4307)) entt::hashed_string{str} +#else +#define HS(str) entt::hashed_string{str} +#endif +``` + +With an example of use included: + +```cpp +constexpr auto identifier = HS("my/resource/identifier"); +``` + +Thanks to [huwpascoe](https://github.com/huwpascoe) for the courtesy. + +## Warning C4003: the min, the max and the macro + +On Windows, a header file defines two macros `min` and `max` which may result in +conflicts with their counterparts in the standard library and therefore in +errors during compilation. + +It's a pretty big problem but fortunately it's not a problem of `EnTT` and there +is a fairly simple solution to it.
+It consists in defining the `NOMINMAX` macro before to include any other header +so as to get rid of the extra definitions: + +```cpp +#define NOMINMAX +``` + +Please refer to [this](https://github.com/skypjack/entt/issues/96) issue for +more details. + +## The standard and the non-copyable types + +`EnTT` uses internally the trait `std::is_copy_constructible_v` to check if a +component is actually copyable. However, this trait doesn't really check whether +a type is actually copyable. Instead, it just checks that a suitable copy +constructor and copy operator exist.
+This can lead to surprising results due to some idiosyncrasies of the standard. + +For example, `std::vector` defines a copy constructor that is conditionally +enabled depending on whether the value type is copyable or not. As a result, +`std::is_copy_constructible_v` returns true for the following specialization: + +```cpp +struct type { + std::vector> vec; +}; +``` + +However, the copy constructor is effectively disabled upon specialization. +Therefore, trying to assign an instance of this type to an entity may trigger a +compilation error.
+As a workaround, users can mark the type explicitly as non-copyable. This also +suppresses the implicit generation of the move constructor and operator, which +will therefore have to be defaulted accordingly: + +```cpp +struct type { + type(const type &) = delete; + type(type &&) = default; + + type & operator=(const type &) = delete; + type & operator=(type &&) = default; + + std::vector> vec; +}; +``` + +Note that aggregate initialization is also disabled as a consequence.
+Fortunately, this type of trick is quite rare. The bad news is that there is no +way to deal with it at the library level, this being due to the design of the +language. On the other hand, the fact that the language itself also offers a way +to mitigate the problem makes it manageable. + +## Which functions trigger which signals + +The `registry` class offers three signals that are emitted following specific +operations. Maybe not everyone knows what these operations are, though.
+If this isn't clear, below you can find a _vademecum_ for this purpose: + +* `on_created` is invoked when a component is first added (neither modified nor + replaced) to an entity. +* `on_update` is called whenever an existing component is modified or replaced. +* `on_destroyed` is called when a component is explicitly or implicitly removed + from an entity. + +Among the most controversial functions can be found `emplace_or_replace` and +`destroy`. However, following the above rules, it's quite simple to know what +will happen.
+In the first case, `on_created` is invoked if the entity has not the component, +otherwise the latter is replaced and therefore `on_update` is triggered. As for +the second case, components are removed from their entities and thus freed when +they are recycled. It means that `on_destroyed` is triggered for every component +owned by the entity that is destroyed. diff --git a/modules/entt/docs/md/lib.md b/modules/entt/docs/md/lib.md index 6564cab..06d8045 100644 --- a/modules/entt/docs/md/lib.md +++ b/modules/entt/docs/md/lib.md @@ -5,151 +5,106 @@ --> # Table of Contents -* [Introduction](#introduction) -* [Named types and traits class](#named-types-and-traits-class) - * [Do not mix types](#do-not-mix-types) -* [Macros, macros everywhere](#macros-macros-everywhere) - * [Conflicts](#conflicts) -* [Allocations: the dark side of the force](#allocations-the-dark-side-of-the-force) +* [Working across boundaries](#working-across-boundaries) + * [Smooth until proven otherwise](#smooth-until-proven-otherwise) + * [Meta context](#meta-context) + * [Memory management](#memory-management) -# Introduction +# Working across boundaries `EnTT` has historically had a limit when used across boundaries on Windows in -general and on GNU/Linux when default visibility was set to _hidden_. The -limitation is due mainly to a custom utility used to assign unique, sequential -identifiers to different types. Unfortunately, this tool is used by several core -classes (the `registry` among the others) that are thus almost unusable across -boundaries.
-The reasons for that are beyond the purposes of this document. However, the good -news is that `EnTT` also offers now a way to overcome this limit and to push -things across boundaries without problems when needed. - -# Named types and traits class - -To allow a type to work properly across boundaries when used by a class that -requires to assign unique identifiers to types, users must specialize a class -template to literally give a compile-time name to the type itself.
-The name of the class template is `name_type_traits` and the specialization must -be such that it exposes a static constexpr data member named `value` having type -either `ENTT_ID_TYPE` or `entt::hashed_string::hash_type`. Its value is the user -defined unique identifier assigned to the specific type.
-Identifiers are not to be sequentially generated in this case. - -As an example: +general and on GNU/Linux when default visibility was set to hidden. The +limitation was mainly due to a custom utility used to assign unique, sequential +identifiers with different types.
+Fortunately, nowadays using `EnTT` across boundaries is much easier. + +## Smooth until proven otherwise + +Many classes in `EnTT` make extensive use of type erasure for their purposes. +This isn't a problem on itself (in fact, it's the basis of an API so convenient +to use). However, a way is needed to recognize the objects whose type has been +erased on the other side of a boundary.
+The `type_hash` class template is how identifiers are generated and thus made +available to the rest of the library. In general, this class doesn't arouse much +interest. The only exception is when a conflict between identifiers occurs +(definitely uncommon though) or when the default solution proposed by `EnTT` +isn't suitable for the user's purposes.
+The section dedicated to `type_info` contains all the details to get around the +issue in a concise and elegant way. Please refer to the specific documentation. + +When working with linked libraries, compile definitions `ENTT_API_EXPORT` and +`ENTT_API_IMPORT` can be used where there is a need to import or export symbols, +so as to make everything work nicely across boundaries.
+On the other hand, everything should run smoothly when working with plugins or +shared libraries that don't export any symbols. + +For anyone who needs more details, the test suite contains multiple examples +covering the most common cases (see the `lib` directory for all details).
+It goes without saying that it's impossible to cover **all** possible cases. +However, what is offered should hopefully serve as a basis for all of them. + +## Meta context + +The runtime reflection system deserves a special mention when it comes to using +it across boundaries.
+Since it's linked already to a static context to which the visible components +are attached and different contexts don't relate to each other, they must be +_shared_ to allow the use of meta types across boundaries. + +Sharing a context is trivial though. First of all, the local one must be +acquired in the main space: ```cpp -struct my_type { /* ... */ }; - -template<> -struct entt::named_type_traits { - static constexpr auto value = "my_type"_hs; -}; +entt::meta_ctx ctx{}; ``` -Because of the rules of the language, the specialization must reside in the -global namespace or in the `entt` namespace. There is no way to change this rule -unfortunately, because it doesn't depend on the library itself. - -The good aspect of this approach is that it's not intrusive at all. The other -way around was in fact forcing users to inherit all their classes from a common -base. Something to avoid, at least from my point of view.
-However, despite the fact that it's not intrusive, it would be great if it was -also easier to use and a bit less error-prone. This is why a bunch of macros -exist to ease defining named types. - -## Do not mix types - -Someone might think that this trick is valid only for the types to push across -boundaries. This isn't how things work. In fact, the problem is more complex -than that.
-As a rule of thumb, users should never mix named and non-named types. Whenever -a type is given a name, all the types must be given a name. As an example, -consider the `registry` class template: in case it is pushed across boundaries, -all the types of components should be assigned a name to avoid subtle bugs. - -Indeed, this constraint can be relaxed in many cases. However, it is difficult -to define a general rule to follow that is not the most stringent, unless users -know exactly what they are doing. Therefore, I won't elaborate on giving further -details on the topic. - -# Macros, macros everywhere - -The library comes with a set of predefined macros to use to declare named types -or export already existing ones. In particular: +Then, it must passed to the receiving space that will set it as its global +context, thus releasing the local one that remains available but is no longer +referred to by the runtime reflection system: -* `ENTT_NAMED_TYPE` can be used to assign a name to already existing types. This - macro must be used in the global namespace even when the types to be named are - not. - - ```cpp - ENTT_NAMED_TYPE(my_type) - ENTT_NAMED_TYPE(ns::another_type) - ``` - -* `ENTT_NAMED_STRUCT` can be used to define and export a struct at the same - time. It accepts also an optional namespace in which to define the given type. - This macro must be used in the global namespace. - - ```cpp - ENTT_NAMED_STRUCT(my_type, { /* struct definition */}) - ENTT_NAMED_STRUCT(ns, another_type, { /* struct definition */}) - ``` - -* `ENTT_NAMED_CLASS` can be used to define and export a class at the same - time. It accepts also an optional namespace in which to define the given type. - This macro must be used in the global namespace. - - ```cpp - ENTT_NAMED_CLASS(my_type, { /* class definition */}) - ENTT_NAMED_CLASS(ns, another_type, { /* class definition */}) - ``` +```cpp +entt::meta_ctx::bind(ctx); +``` -Nested namespaces are supported out of the box as well in all cases. As an -example: +From now on, both spaces will refer to the same context and on it will be +attached the new visible meta types, no matter where they are created.
+A context can also be reset and then associated again locally as: ```cpp -ENTT_NAMED_STRUCT(nested::ns, my_type, { /* struct definition */}) +entt::meta_ctx::bind(entt::meta_ctx{}); ``` -These macros can be used to avoid specializing the `named_type_traits` class -template. In all cases, the name of the class is used also as a seed to generate -the compile-time unique identifier. - -## Conflicts - -When using macros, unique identifiers are 32/64 bit integers generated by -hashing strings during compilation. Therefore, conflicts are rare but still -possible. In case of conflicts, everything simply will get broken at runtime and -the strangest things will probably take place.
-Unfortunately, there is no safe way to prevent it. If this happens, it will be -enough to give a different value to one of the conflicting types to solve the -problem. To do this, users can either assign a different name to the class or -directly define a specialization for the `named_type_traits` class template. - -# Allocations: the dark side of the force - -As long as `EnTT` won't support custom allocators, another problem with -allocations will remain alive instead. This is in fact easily solved, or at -least it is if one knows it. - -To allow users to add types dynamically, the library makes extensive use of type -erasure techniques and dynamic allocations for pools (whether they are for -components, events or anything else). The problem occurs when, for example, a -registry is created on one side of a boundary and a pool is dynamically created -on the other side. In the best case, everything will crash at the exit, while at -worst it will do so at runtime.
-To avoid problems, the pools must be generated from the same side of the -boundary where the object that owns them is also created. As an example, when -the registry is created in the main executable and used across boundaries for a -given type of component, the pool for that type must be created before passing -around the registry itself. To do this is fortunately quite easy, since it is -sufficient to invoke any of the methods that involve the given type (continuing -the example with the registry, a call to `reserve` or `size` is more than -enough). - -Maybe one day some dedicated methods will be added that do nothing but create a -pool for a given type. Until now it has been preferred to keep the API cleaner -as they are not strictly necessary. +This is allowed because local and global contexts are separated. Therefore, it's +always possible to make the local context the current one again. + +Before to release a context, all locally registered types should be reset to +avoid dangling references. Otherwise, if a type is accessed from another space +by name, there could be an attempt to address its parts that are no longer +available. + +## Memory Management + +There is another subtle problem due to memory management that can lead to +headaches.
+It can occur where there are pools of objects (such as components or events) +dynamically created on demand. This is usually not a problem when working with +linked libraries that rely on the same dynamic runtime. However, it can occur in +the case of plugins or statically linked runtimes. + +As an example, imagine creating an instance of `registry` in the main executable +and sharing it with a plugin. If the latter starts working with a component that +is unknown to the former, a dedicated pool is created within the registry on +first use.
+As one can guess, this pool is instantiated on a different side of the boundary +from the `registry`. Therefore, the instance is now managing memory from +different spaces and this can quickly lead to crashes if not properly addressed. + +To overcome the risk, it's recommended to use well-defined interfaces that make +fundamental types pass through the boundaries, isolating the instances of the +`EnTT` classes from time to time and as appropriate.
+Refer to the test suite for some examples, read the documentation available +online about this type of issues or consult someone who has already had such +experiences to avoid problems. diff --git a/modules/entt/docs/md/links.md b/modules/entt/docs/md/links.md index 6ab64f2..7327ea7 100644 --- a/modules/entt/docs/md/links.md +++ b/modules/entt/docs/md/links.md @@ -3,8 +3,8 @@ `EnTT` is widely used in private and commercial applications. I cannot even mention most of them because of some signatures I put on some documents time ago. Fortunately, there are also people who took the time to implement open -source projects based on `EnTT` and did not hold back when it came to -documenting them. +source projects based on `EnTT` and didn't hold back when it came to documenting +them. Below an incomplete list of games, applications and articles that can be used as a reference. Where I put the word _apparently_ means that the use of `EnTT` is @@ -17,48 +17,170 @@ I hope this list can grow much more in the future: * [Minecraft](https://minecraft.net/en-us/attribution/) by [Mojang](https://mojang.com/): of course, **that** Minecraft, see the open source attributions page for more details. + * [Minecraft Earth](https://www.minecraft.net/en-us/about-earth) by + [Mojang](https://mojang.com/): an augmented reality game for mobile, that + lets users bring Minecraft into the real world. + * [Ember Sword](https://embersword.com/): a modern Free-to-Play MMORPG with a + player-driven economy, a classless combat system, and scarce, tradable + cosmetic collectibles. + * Apparently [Diablo II: Resurrected](https://diablo2.blizzard.com/) by + [Blizzard](https://www.blizzard.com/): monsters, heroes, items, spells, all + resurrected. Thanks unknown insider. + * [Apparently](https://www.youtube.com/watch?v=P8xvOA3ikrQ&t=1105s) + [Call of Duty: Vanguard](https://www.callofduty.com/vanguard) by + [Sledgehammer Games](https://www.sledgehammergames.com/): I can neither + confirm nor deny but there is a license I know in the credits. + * Apparently [D&D Dark Alliance](https://darkalliance.wizards.com) by + [Wizards of the Coast](https://company.wizards.com): your party, their + funeral. + * [TiltedOnline](https://github.com/tiltedphoques/TiltedOnline) by + [Tilted Phoques](https://github.com/tiltedphoques): Skyrim and Fallout 4 mod + to play online. + * [Antkeeper](https://github.com/antkeeper/antkeeper-source): an ant colony + simulation [game](https://antkeeper.com/). + * [Openblack](https://github.com/openblack/openblack): open source + reimplementation of the game _Black & White_ (2001). + * [Land of the Rair](https://github.com/LandOfTheRair/core2): the new backend + of [a retro-style MUD](https://rair.land/) for the new age. * [Face Smash](https://play.google.com/store/apps/details?id=com.gamee.facesmash): a game to play with your face. * [EnTT Pacman](https://github.com/Kerndog73/EnTT-Pacman): an example of how to make Pacman with `EnTT`. + * [Wacman](https://github.com/carlfindahl/wacman): a pacman clone with OpenGL. * [Classic Tower Defence](https://github.com/kerndog73/Classic-Tower-Defence): a tiny little tower defence game featuring a homemade font. [Check it out](https://indi-kernick.itch.io/classic-tower-defence). * [The Machine](https://github.com/Kerndog73/The-Machine): a box pushing puzzler with logic gates and other cool stuff. [Check it out](https://indi-kernick.itch.io/the-machine-web-version). - * [EnttPong](https://github.com/reworks/EnttPong): an example of how to make - Pong with `EnTT`. + * [EnTTPong](https://github.com/DomRe/EnttPong): a basic game made to showcase + different parts of EnTT and C++17. * [Randballs](https://github.com/gale93/randballs): simple `SFML` and `EnTT` playground. * [EnTT Tower Defense](https://github.com/Daivuk/tddod): a data oriented tower defense example. * [EnTT Breakout](https://github.com/vblanco20-1/entt-breakout): simple example of a breakout game, using `SDL` and `EnTT`. + * [Arcade puzzle game with EnTT](https://github.com/MasonRG/ArcadePuzzleGame): + arcade puzzle game made in C++ using the `SDL2` and `EnTT` libraries. + * [Snake with EnTT](https://github.com/MasonRG/SnakeGame): simple snake game + made in C++ with the `SDL2` and `EnTT` libraries. + * [Mirrors lasers and robots](https://github.com/guillaume-haerinck/imac-tower-defense): + a small tower defense game based on mirror orientation. + * [PopHead](https://github.com/SPC-Some-Polish-Coders/PopHead/): 2D, Zombie, + RPG game made from scratch in C++. + * [Robotligan](https://github.com/Trisslotten/robotligan): multiplayer + football game. + * [DungeonSlayer](https://github.com/alohaeee/DungeonSlayer): 2D game made + from scratch in C++. + * [3DGame](https://github.com/kwarkGorny/3DGame): 2.5D top-down space shooter. + * [Pulcher](https://github.com/AODQ/pulcher): 2D cross-platform game inspired + by Quake. + * [Destroid](https://github.com/tyrannicaltoucan/destroid): _one-bazillionth_ + arcade game about shooting dirty rocks in space, inspired by Asteroids. + * [Wanderer](https://github.com/albin-johansson/wanderer): a 2D exploration + based indie game. + * [Spelunky Classic remake](https://github.com/dbeef/spelunky-psp): a truly + multiplatform experience with a rewrite from scratch. + * [CubbyTower](https://github.com/utilForever/CubbyTower): a simple tower + defense game using C++ with Entity Component System (ECS). + * [Runeterra](https://github.com/utilForever/Runeterra): Legends of Runeterra + simulator using C++ with some reinforcement learning. + * [Black Sun](https://store.steampowered.com/app/1670930/Black_Sun/): fly your + space ship through a large 2D open world. + * [PokeMaster](https://github.com/utilForever/PokeMaster): Pokemon Battle + simulator using C++ with some reinforcement learning. + * [HomeHearth](https://youtu.be/GrEWl8npL9Y): choose your hero, protect the + town, before it's too late. + * [City Builder Game](https://github.com/PhiGei2000/CityBuilderGame): a simple + city-building game using C++ and OpenGL. -* Engines/Frameworks: - * [The Forge](https://github.com/ConfettiFX/The-Forge) by - [Confett](http://www.confettispecialfx.com/): a cross-platform rendering - framework. +* Engines and the like: + * [Aether Engine](https://hadean.com/spatial-simulation/) + [v1.1+](https://docs.hadean.com/v1.1/Licenses/) by + [Hadean](https://hadean.com/): a library designed for spatially partitioning + agent-based simulations. + * [Fling Engine](https://github.com/flingengine/FlingEngine): a Vulkan game + engine with a focus on data oriented design. + * [NovusCore](https://github.com/novuscore/NovusCore): a modern take on World + of Warcraft emulation. + * [Chrysalis](https://github.com/ivanhawkes/Chrysalis): action RPG SDK for + CRYENGINE games. + * [LM-Engine](https://github.com/Lawrencemm/LM-Engine): the Vim of game + engines. + * [Edyn](https://github.com/xissburg/edyn): a real-time physics engine + organized as an ECS. + * [MushMachine](https://github.com/MadeOfJelly/MushMachine): engine... + vrooooommm. + * [Antara Gaming SDK](https://github.com/KomodoPlatform/antara-gaming-sdk): + the Komodo Gaming Software Development Kit. + * [XVP](https://ravingbots.com/xvp-expansive-vehicle-physics-for-unreal-engine/): + [_eXpansive Vehicle Physics_](https://github.com/raving-bots/xvp/wiki/Plugin-integration-guide) + plugin for Unreal Engine. * [Apparently](https://teamwisp.github.io/credits/) [Wisp](https://teamwisp.github.io/product/) by [Team Wisp](https://teamwisp.github.io/): an advanced real-time ray tracing renderer built for the demands of video game artists. - * [starlight](https://github.com/DomRe/starlight): game programming framework - using `Allegro`, `Lua` and modern C++. - * [Apparently](https://github.com/JosiahWI/qub3d-libdeps) - [Qub3d](https://qub3d.org/): because blocks should be open source. * [shiva](https://github.com/Milerius/shiva): modern C++ engine with modularity. + * [ImGui/EnTT editor](https://github.com/Green-Sky/imgui_entt_entity_editor): + a drop-in, single-file entity editor for `EnTT` that uses `ImGui` as + graphical backend (with + [demo code](https://github.com/Green-Sky/imgui_entt_entity_editor_demo)). + * [SgOgl](https://github.com/stwe/SgOgl): a game engine library for OpenGL + developed for educational purposes. + * [Lumos](https://github.com/jmorton06/Lumos): game engine written in C++ + using OpenGL and Vulkan. + * [Silvanus](https://github.com/hobbyistmaker/silvanus): Silvanus Fusion 360 + Box Generator. + * [Lina Engine](https://github.com/inanevin/LinaEngine): an open-source, + modular, tiny and fast C++ game engine, aimed to develop 3D desktop games. + * [Spike](https://github.com/FahimFuad/Spike): a powerful game engine which + can run on a toaster. + * [Helena Framework](https://github.com/NIKEA-SOFT/HelenaFramework): a modern + framework in C++17 for backend development. + * [Unity/EnTT](https://github.com/TongTungGiang/unity-entt): tech demo of a + native simulation layer using `EnTT` and `Unity` as a rendering engine. + * [OverEngine](https://github.com/OverShifted/OverEngine): an over-engineered + game engine. + * [Electro](https://github.com/Electro-Technologies/Electro): high performance + 3D game engine with a high emphasis on rendering. + * [Kawaii](https://github.com/Mathieu-Lala/Kawaii_Engine): a modern data + oriented game engine. + * [Becketron](https://github.com/Doctor-Foxling/Becketron): a game engine + written mostly in C++. + * [Spatial Engine](https://github.com/luizgabriel/Spatial.Engine): a + cross-platform engine created on top of google's filament rendering engine. + * [Kaguya](https://github.com/KaiH0717/Kaguya): D3D12 Rendering Engine. + * [OpenAWE](https://github.com/OpenAWE-Project/OpenAWE): open implementation + of the Alan Wake Engine. + * [Nazara Engine](https://github.com/DigitalPulseSoftware/NazaraEngine): fast, + cross-platform, object-oriented API to help in daily developer life. -* Emulators: - * [NovusCore](https://github.com/novuscore/NovusCore): A modern take on World - of Warcraft emulation. - -* Articles and blog posts +* Articles, videos and blog posts: * [Some posts](https://skypjack.github.io/tags/#entt) on my personal [blog](https://skypjack.github.io/) are about `EnTT`, for those who want to know **more** on this project. + * [Game Engine series](https://www.youtube.com/c/TheChernoProject/videos) by + [The Cherno](https://github.com/TheCherno) (not only about `EnTT` but also + on the use of an ECS in general): + - [Intro to EnTT](https://www.youtube.com/watch?v=D4hz0wEB978). + - [Entities and Components](https://www.youtube.com/watch?v=-B1iu4QJTUc). + - [The ENTITY Class](https://www.youtube.com/watch?v=GfSzeAcsBb0). + - [Camera Systems](https://www.youtube.com/watch?v=ubZn7BlrnTU). + - [Scene Camera](https://www.youtube.com/watch?v=UKVFRRufKzo). + - [Native Scripting](https://www.youtube.com/watch?v=iIUhg88MK5M). + - [Native Scripting (now with virtual functions!)](https://www.youtube.com/watch?v=1cHEcrIn8IQ). + - [Scene Hierarchy Panel](https://www.youtube.com/watch?v=wziDnE8guvI). + - [Properties Panel](https://www.youtube.com/watch?v=NBpB0qscF3E). + - [Camera Component UI](https://www.youtube.com/watch?v=RIMt_6agUiU). + - [Drawing Component UI](https://www.youtube.com/watch?v=u3yq8s3KuSE). + - [Transform Component UI](https://www.youtube.com/watch?v=8JqcXYbzPJc). + - [Adding/Removing Entities and Components UI](https://www.youtube.com/watch?v=PsyGmsIgp9M). + - [Saving and Loading Scenes](https://www.youtube.com/watch?v=IEiOP7Y-Mbc). + - ... And so on. + [Check out](https://www.youtube.com/channel/UCQ-W1KE9EYfdxhL6S4twUNw) the + _Game Engine Series_ by The Cherno for more videos. * [Space Battle: Huge edition](http://victor.madtriangles.com/code%20experiment/2018/06/11/post-ecs-battle-huge.html): huge space battle built entirely from scratch. * [Space Battle](https://github.com/vblanco20-1/ECS_SpaceBattle): huge space @@ -69,20 +191,56 @@ I hope this list can grow much more in the future: giant space battle. * [Conan Adventures (SFML and EnTT in C++)](https://leinnan.github.io/blog/conan-adventuressfml-and-entt-in-c.html): create projects in modern C++ using `SFML`, `EnTT`, `Conan` and `CMake`. + * [Adding EnTT ECS to Chrysalis](https://www.tauradius.com/post/adding-an-ecs-to-chrysalis/): + a blog entry (and its + [follow-up](https://www.tauradius.com/post/chrysalis-update-2020-08-02/)) + about the integration of `EnTT` into `Chrysalis`, an action RPG SDK for + CRYENGINE games. + * [Creating Minecraft in One Week with C++ and Vulkan](https://vazgriz.com/189/creating-minecraft-in-one-week-with-c-and-vulkan/): + a crack at recreating Minecraft in one week using a custom C++ engine and + Vulkan ([code included](https://github.com/vazgriz/VoxelGame)). + * [Ability Creator](https://www.erichildebrand.net/blog/ability-creator-project-retrospect): + project retrospect by [Eric Hildebrand](https://www.erichildebrand.net/). + * [EnTT Entity Component System Gaming Library](https://gamefromscratch.com/entt-entity-component-system-gaming-library/): + `EnTT` on GameFromScratch.com. * Any Other Business: - * The [ArcGIS Runtime SDKs](https://developers.arcgis.com/arcgis-runtime/) - by [Esri](https://www.esri.com/): they use `EnTT` for the internal ECS and - the cross platform C++ rendering engine. The SDKs are utilized by a lot of + * [ArcGIS Runtime SDKs](https://developers.arcgis.com/arcgis-runtime/) by + [Esri](https://www.esri.com/): they use `EnTT` for the internal ECS and the + cross platform C++ rendering engine. The SDKs are utilized by a lot of enterprise custom apps, as well as by Esri for its own public applications such as [Explorer](https://play.google.com/store/apps/details?id=com.esri.explorer), [Collector](https://play.google.com/store/apps/details?id=com.esri.arcgis.collector) and [Navigator](https://play.google.com/store/apps/details?id=com.esri.navigator). + * [FASTSUITE Edition 2](https://www.fastsuite.com/en_EN/fastsuite/fastsuite-edition-2.html) + by [Cenit](http://www.cenit.com/en_EN/about-us/overview.html): they use + `EnTT` to drive their simulation, that is, the communication between robot + controller emulator and renderer. + * [Ragdoll](https://ragdolldynamics.com/): real-time physics for Autodesk Maya + 2020. + * [Project Lagrange](https://github.com/adobe/lagrange): a robust geometry + processing library by [Adobe](https://github.com/adobe). + * [AtomicDEX](https://github.com/KomodoPlatform/atomicDEX-Desktop): a secure + wallet and non-custodial decentralized exchange rolled into one application. * [Apparently](https://www.linkedin.com/in/skypjack/) [NIO](https://www.nio.io/): there was a collaboration to make some changes to `EnTT`, at the time used for internal projects. + * [Apparently](https://www.linkedin.com/jobs/view/architekt-c%2B%2B-at-tieto-1219512333/) + [Tieto](https://www.tieto.com/): they published a job post where `EnTT` was + listed on their software stack. + * [Sequentity](https://github.com/alanjfs/sequentity): A MIDI-like + sequencer/tracker for C++ and `ImGui` (with `Magnum` and `EnTT`). + * [EnTT meets Sol2](https://github.com/skaarj1989/entt-meets-sol2): freely + available examples of how to combine `EnTT` and `Sol2`. + * [Godot meets EnTT](https://github.com/portaloffreedom/godot_entt_example/): + a simple example on how to use `EnTT` within + [`Godot`](https://godotengine.org/). + * [Godot and GameNetworkingSockets meet EnTT](https://github.com/portaloffreedom/godot_entt_net_example): + a simple example on how to use `EnTT` and + [`GameNetworkingSockets`](https://github.com/ValveSoftware/GameNetworkingSockets) + within [`Godot`](https://godotengine.org/). * [MatchOneEntt](https://github.com/mhaemmerle/MatchOneEntt): port of [Match One](https://github.com/sschmid/Match-One) for `Entitas-CSharp`. * GitHub contains also diff --git a/modules/entt/docs/md/locator.md b/modules/entt/docs/md/locator.md index 77a97da..fa6023d 100644 --- a/modules/entt/docs/md/locator.md +++ b/modules/entt/docs/md/locator.md @@ -14,62 +14,43 @@ # Introduction Usually service locators are tightly bound to the services they expose and it's -hard to define a general purpose solution. This template based implementation -tries to fill the gap and to get rid of the burden of defining a different -specific locator for each application.
-This class is tiny, partially unsafe and thus risky to use. Moreover it doesn't -fit probably most of the scenarios in which a service locator is required. Look -at it as a small tool that can sometimes be useful if users know how to handle -it. +hard to define a general purpose solution.
+This tiny class tries to fill the gap and to get rid of the burden of defining a +different specific locator for each application. # Service locator -The API is straightforward. The basic idea is that services are implemented by -means of interfaces and rely on polymorphism.
-The locator is instantiated with the base type of the service if any and a -concrete implementation is provided along with all the parameters required to -initialize it. As an example: +The service locator API tries to mimic that of `std::optional` and adds some +extra functionalities on top of it such as allocator support.
+There are a couple of functions to set up a service, namely `emplace` and +`allocate_emplace`: ```cpp -// the service has no base type, a locator is used to treat it as a kind of singleton -entt::service_locator::set(params...); - -// sets up an opaque service -entt::service_locator::set(params...); - -// resets (destroys) the service -entt::service_locator::reset(); +entt::locator::emplace(argument); +entt::locator::allocate_emplace(allocator, argument); ``` -The locator can also be queried to know if an active service is currently set -and to retrieve it if necessary (either as a pointer or as a reference): +The difference is that the latter expects an allocator as the first argument and +uses it to allocate the service itself.
+Once a service has been set up, it's retrieved using the value function: ```cpp -// no service currently set -auto empty = entt::service_locator::empty(); - -// gets a (possibly empty) shared pointer to the service ... -std::shared_ptr ptr = entt::service_locator::get(); - -// ... or a reference, but it's undefined behaviour if the service isn't set yet -audio_interface &ref = entt::service_locator::ref(); +interface &service = entt::locator::value(); ``` -A common use is to wrap the different locators in a container class, creating -aliases for the various services: +Since the service may not be set (and therefore this function may result in an +undefined behavior), the `has_value` and `value_or` functions are also available +to test a service locator and to get a fallback service in case there is none: ```cpp -struct locator { - using camera = entt::service_locator; - using audio = entt::service_locator; - // ... -}; - -// ... - -void init() { - locator::camera::set(); - locator::audio::set(params...); +if(entt::locator::has_value()) { // ... } + +interface &service = entt::locator::value_or(argument); ``` + +All arguments are used only if necessary, that is, if a service doesn't already +exist and therefore the fallback service is constructed and returned. In all +other cases, they are discarded.
+Finally, to reset a service, use the `reset` function. diff --git a/modules/entt/docs/md/meta.md b/modules/entt/docs/md/meta.md index 84a8bf9..f50e70f 100644 --- a/modules/entt/docs/md/meta.md +++ b/modules/entt/docs/md/meta.md @@ -1,4 +1,4 @@ -# Crash Course: reflection system +# Crash Course: runtime reflection system # Introduction -Reflection (or rather, its lack) is a trending topic in the C++ world and, in -the specific case of `EnTT`, a tool that can unlock a lot of other features. I +Reflection (or rather, its lack) is a trending topic in the C++ world and a tool +that can unlock a lot of interesting feature in the specific case of `EnTT`. I looked for a third-party library that met my needs on the subject, but I always came across some details that I didn't like: macros, being intrusive, too many -allocations. In one word: unsatisfactory.
+allocations, and so on.
I finally decided to write a built-in, non-intrusive and macro-free runtime reflection system for `EnTT`. Maybe I didn't do better than others or maybe yes, time will tell me, but at least I can model this tool around the library to -which it belongs and not vice versa. +which it belongs and not the opposite. + +# Names and identifiers + +The meta system doesn't force users to rely on the tools provided by the library +when it comes to working with names and identifiers. It does this by offering an +API that works with opaque identifiers that may or may not be generated by means +of a hashed string.
+This means that users can assign any type of identifier to the meta objects, as +long as they are numeric. It doesn't matter if they are generated at runtime, at +compile-time or with custom functions. + +That being said, the examples in the following sections are all based on the +`hashed_string` class as provided by this library. Therefore, where an +identifier is required, it's likely that an user defined literal is used as +follows: + +```cpp +auto factory = entt::meta().type("reflected_type"_hs); +``` + +For what it's worth, this is likely completely equivalent to: + +```cpp +auto factory = entt::meta().type(42); +``` + +Obviously, human-readable identifiers are more convenient to use and highly +recommended. # Reflection in a nutshell Reflection always starts from real types (users cannot reflect imaginary types and it would not make much sense, we wouldn't be talking about reflection anymore).
-To _reflect_ a type, the library provides the `reflect` function: +To create a meta node, the library provides the `meta` function that accepts a +type to reflect as a template parameter: + +```cpp +auto factory = entt::meta(); +``` + +This isn't enough to _export_ the given type and make it visible though.
+The returned value is a factory object to use to continue building the meta +type. In order to make the type _visible_, users can assign it an identifier: ```cpp -auto factory = entt::reflect("reflected_type"); +auto factory = entt::meta().type("reflected_type"_hs); ``` -It accepts the type to reflect as a template parameter and an optional name as -an argument. Names are important because users can retrieve meta types at -runtime by searching for them by name. However, there are cases in which users -can be interested in adding features to a reflected type so that the reflection -system can use it correctly under the hood, but they don't want to allow -searching the type by name.
-In both cases, the returned value is a factory object to use to continue -building the meta type. +Or use the default one, that is, the built-in identifier for the given type: + +```cpp +auto factory = entt::meta().type(); +``` -A factory is such that all its member functions returns the factory itself. -It can be used to extend the reflected type and add the following: +Identifiers are important because users can retrieve meta types at runtime by +searching for them by _name_ other than by type.
+On the other hand, there are cases in which users can be interested in adding +features to a reflected type so that the reflection system can use it correctly +under the hood, but they don't want to also make the type _searchable_. In this +case, it's sufficient not to invoke `type`. + +A factory is such that all its member functions return the factory itself or a +decorated version of it. This object can be used to add the following: * _Constructors_. Actual constructors can be assigned to a reflected type by specifying their list of arguments. Free functions (namely, factories) can be @@ -59,55 +107,73 @@ It can be used to extend the reflected type and add the following: Use the `ctor` member function for this purpose: ```cpp - entt::reflect("reflected").ctor().ctor<&factory>(); + entt::meta().ctor().ctor<&factory>(); ``` -* _Destructors_. Free functions can be set as destructors of reflected types. - The purpose is to give users the ability to free up resources that require - special treatment before an object is actually destroyed.
+* _Destructors_. Free functions and member functions can be used as destructors + of reflected types. The purpose is to give users the ability to free up + resources that require special treatment before an object is actually + destroyed.
Use the `dtor` member function for this purpose: ```cpp - entt::reflect("reflected").dtor<&destroy>(); + entt::meta().dtor<&destroy>(); ``` + A function should neither delete nor explicitly invoke the destructor of a + given instance. + * _Data members_. Both real data members of the underlying type and static and global variables, as well as constants of any kind, can be attached to a meta - type. From a client's point of view, all the variables associated with the - reflected type will appear as if they were part of the type itself.
+ type. From the point of view of the client, all the variables associated with + the reflected type will appear as if they were part of the type itself.
Use the `data` member function for this purpose: ```cpp - entt::reflect("reflected") - .data<&my_type::static_variable>("static") - .data<&my_type::data_member>("member") - .data<&global_variable>("global"); + entt::meta() + .data<&my_type::static_variable>("static"_hs) + .data<&my_type::data_member>("member"_hs) + .data<&global_variable>("global"_hs); + ``` + + The function requires as an argument the identifier to give to the meta data + once created. Users can then access meta data at runtime by searching for them + by _name_.
+ Data members can also be defined by means of a setter and getter pair. Setters + and getters can be either free functions, class members or a mix of them, as + long as they respect the required signatures. This approach is also convenient + to create a read-only variable from a non-const data member: + + ```cpp + entt::meta().data("member"_hs); + ``` + + Multiple setters are also supported by means of a `value_list` object: + + ```cpp + entt::meta().data, &my_type::data_member>("member"_hs); ``` - This function requires as an argument the name to give to the meta data once - created. Users can then access meta data at runtime by searching for them by - name.
- Data members can be set also by means of a couple of functions, namely a - setter and a getter. Setters and getters can be either free functions, member - functions or mixed ones, as long as they respect the required signatures.
Refer to the inline documentation for all the details. * _Member functions_. Both real member functions of the underlying type and free - functions can be attached to a meta type. From a client's point of view, all - the functions associated with the reflected type will appear as if they were - part of the type itself.
+ functions can be attached to a meta type. From the point of view of the + client, all the functions associated with the reflected type will appear as if + they were part of the type itself.
Use the `func` member function for this purpose: ```cpp - entt::reflect("reflected") - .func<&my_type::static_function>("static") - .func<&my_type::member_function>("member") - .func<&free_function>("free"); + entt::meta() + .func<&my_type::static_function>("static"_hs) + .func<&my_type::member_function>("member"_hs) + .func<&free_function>("free"_hs); ``` - This function requires as an argument the name to give to the meta function - once created. Users can then access meta functions at runtime by searching for - them by name. + The function requires as an argument the identifier to give to the meta + function once created. Users can then access meta functions at runtime by + searching for them by _name_.
+ Overloading of meta functions is supported. Overloaded functions are resolved + at runtime by the reflection system according to the types of the arguments. * _Base classes_. A base class is such that the underlying type is actually derived from it. In this case, the reflection system tracks the relationship @@ -115,7 +181,7 @@ It can be used to extend the reflected type and add the following: Use the `base` member function for this purpose: ```cpp - entt::reflect("derived").base(); + entt::meta().base(); ``` From now on, wherever a `base_type` is required, an instance of `derived_type` @@ -128,7 +194,7 @@ It can be used to extend the reflected type and add the following: Use the `conv` member function for this purpose: ```cpp - entt::reflect().conv(); + entt::meta().conv(); ``` That's all, everything users need to create meta types and enjoy the reflection @@ -138,192 +204,640 @@ Also, do not forget what these few lines hide under the hood: a built-in, non-intrusive and macro-free system for reflection in C++. Features that are definitely worth the price, at least for me. -# Any as in any type - -The reflection system comes with its own meta any type. It may seem redundant -since C++17 introduced `std::any`, but it is not.
-In fact, the _type_ returned by an `std::any` is a const reference to an -`std::type_info`, an implementation defined class that's not something everyone -wants to see in a software. Furthermore, the class `std::type_info` suffers from -some design flaws and there is even no way to _convert_ an `std::type_info` into -a meta type, thus linking the two worlds. - -A meta any object provides an API similar to that of its most famous counterpart -and serves the same purpose of being an opaque container for any type of -value.
-It minimizes the allocations required, which are almost absent thanks to _SBO_ -techniques. In fact, unless users deal with _fat types_ and create instances of -them though the reflection system, allocations are at zero. - -A meta any object can be created by any other object or as an empty container -to initialize later: +## Any to the rescue + +The reflection system offers a kind of _extended version_ of the `entt::any` +class (see the core module for more details).
+The purpose is to add some feature on top of those already present, so as to +integrate it with the meta type system without having to duplicate the code. + +The API is very similar to that of the `any` type. The class `meta_any` _wraps_ +many of the feature to infer a meta node, before forwarding some or all of the +arguments to the underlying storage.
+Among the few relevant differences, `meta_any` adds support for containers and +pointer-like types (see the following sections for more details), while `any` +does not.
+Similar to `any`, this class can also be used to create _aliases_ for unmanaged +objects either with `forward_as_meta` or using the `std::in_place_type` +disambiguation tag, as well as from an existing object by means of the `as_ref` +member function. However, unlike `any`, `meta_any` treats an empty instance and +one initialized with `void` differently: ```cpp -// a meta any object that contains an int -entt::meta_any any{0}; - -// an empty meta any object entt::meta_any empty{}; +entt::meta_any other{std::in_place_type}; ``` -It can be constructed or assigned by copy and move and it takes the burden of -destroying the contained object when required.
-A meta any object has a `type` member function that returns the meta type of the -contained value, if any. The member functions `can_cast` and `can_convert` are -used to know if the underlying object has a given type as a base or if it can be -converted implicitly to it. Similarly, `cast` and `convert` do what they promise -and return the expected value. +While `any` considers both as empty, `meta_any` treats objects initialized with +`void` as if they were _valid_ ones. This allows to differentiate between failed +function calls and function calls that are successful but return nothing.
+Finally, the member functions `try_cast`, `cast` and `allow_cast` are used to +cast the underlying object to a given type (either a reference or a value type) +or to _convert_ a `meta_any` in such a way that a cast becomes viable for the +resulting object. There is in fact no `any_cast` equivalent for `meta_any`. -# Enjoy the runtime +## Enjoy the runtime Once the web of reflected types has been constructed, it's a matter of using it at runtime where required.
-All this has the great merit that, unlike the vast majority of the things -present in this library and closely linked to the compile-time, the reflection -system stands in fact as a non-intrusive tool for the runtime. +All this has the great merit that the reflection system stands in fact as a +non-intrusive tool for the runtime, unlike the vast majority of the things +offered by this library and closely linked to the compile-time. -To search for a reflected type there are two options: by type or by name. In -both cases, the search can be done by means of the `resolve` function: +To search for a reflected type there are a few options: ```cpp -// search for a reflected type by type +// direct access to a reflected type auto by_type = entt::resolve(); -// search for a reflected type by name -auto by_name = entt::resolve("reflected_type"); +// look up a reflected type by identifier +auto by_id = entt::resolve("reflected_type"_hs); + +// look up a reflected type by type info +auto by_type_id = entt::resolve(entt::type_id()); ``` -There exits also a third overload of the `resolve` function to use to iterate -all the reflected types at once: +There exits also an overload of the `resolve` function to use to iterate all the +reflected types at once. It returns an iterable object that can be used in a +range-for loop: ```cpp -resolve([](auto type) { +for(auto type: entt::resolve()) { // ... -}); +} ``` -In all cases, the returned value is an instance of `meta_type`. This type of -objects offer an API to know the _runtime name_ of the type, to iterate all the -meta objects associated with them and even to build or destroy instances of the -underlying type.
+In all cases, the returned value is an instance of `meta_type`. This kind of +objects offer an API to know their _runtime identifiers_, to iterate all the +meta objects associated with them and even to build instances of the underlying +type.
Refer to the inline documentation for all the details. The meta objects that compose a meta type are accessed in the following ways: -* _Meta constructors_. They are accessed by types of arguments: +* _Meta data_. They are accessed by _name_: ```cpp - auto ctor = entt::resolve().ctor(); + auto data = entt::resolve().data("member"_hs); ``` - The returned type is `meta_ctor` and may be invalid if there is no constructor - that accepts the supplied arguments or at least some types from which they are - derived or to which they can be converted.
- A meta constructor offers an API to know the number of arguments, the expected - meta types and to invoke it, therefore to construct a new instance of the - underlying type. + The returned type is `meta_data` and may be invalid if there is no meta data + object associated with the given identifier.
+ A meta data object offers an API to query the underlying type (for example, to + know if it's a const or a static one), to get the meta type of the variable + and to set or get the contained value. -* _Meta destructor_. It's returned by a dedicated function: +* _Meta functions_. They are accessed by _name_: ```cpp - auto dtor = entt::resolve().dtor(); + auto func = entt::resolve().func("member"_hs); ``` - The returned type is `meta_dtor` and may be invalid if there is no custom - destructor set for the given meta type.
- All what a meta destructor has to offer is a way to invoke it on a given - instance. Be aware that the result may not be what is expected. + The returned type is `meta_func` and may be invalid if there is no meta + function object associated with the given identifier.
+ A meta function object offers an API to query the underlying type (for + example, to know if it's a const or a static function), to know the number of + arguments, the meta return type and the meta types of the parameters. In + addition, a meta function object can be used to invoke the underlying function + and then get the return value in the form of a `meta_any` object. -* _Meta data_. They are accessed by name: +* _Meta bases_. They are accessed through the _name_ of the base types: ```cpp - auto data = entt::resolve().data("member"); + auto base = entt::resolve().base("base"_hs); ``` - The returned type is `meta_data` and may be invalid if there is no meta data - object associated with the given name.
- A meta data object offers an API to query the underlying type (ie to know if - it's a const or a static one), to get the meta type of the variable and to set - or get the contained value. + The returned type is `meta_type` and may be invalid if there is no meta base + object associated with the given identifier. + +All the objects thus obtained as well as the meta types can be explicitly +converted to a boolean value to check if they are valid: + +```cpp +if(auto func = entt::resolve().func("member"_hs); func) { + // ... +} +``` + +Furthermore, all them are also returned by specific overloads that provide the +caller with iterable ranges of top-level elements. As an example: + +```cpp +for(auto data: entt::resolve().data()) { + // ... +} +``` + +A meta type can be used to `construct` actual instances of the underlying +type.
+In particular, the `construct` member function accepts a variable number of +arguments and searches for a match. It then returns a `meta_any` object that may +or may not be initialized, depending on whether a suitable constructor has been +found or not. + +There is no object that wraps the destructor of a meta type nor a `destroy` +member function in its API. Destructors are invoked implicitly by `meta_any` +behind the scenes and users have not to deal with them explicitly. Furthermore, +they have no name, cannot be searched and wouldn't have member functions to +expose anyway.
+Similarly, conversion functions aren't directly accessible. They are used +internally by `meta_any` and the meta objects when needed. + +Meta types and meta objects in general contain much more than what is said: a +plethora of functions in addition to those listed whose purposes and uses go +unfortunately beyond the scope of this document.
+I invite anyone interested in the subject to look at the code, experiment and +read the inline documentation to get the best out of this powerful tool. + +## Container support + +The runtime reflection system also supports containers of all types.
+Moreover, _containers_ doesn't necessarily mean those offered by the C++ +standard library. In fact, user defined data structures can also work with the +meta system in many cases. + +To make a container be recognized as such by the meta system, users are required +to provide specializations for either the `meta_sequence_container_traits` class +or the `meta_associative_container_traits` class, according with the actual type +of the container.
+`EnTT` already exports the specializations for some common classes. In +particular: + +* `std::vector` and `std::array` are exported as _sequence containers_. +* `std::map`, `std::set` and their unordered counterparts are exported as + _associative containers_. + +It's important to include the header file `container.hpp` to make these +specializations available to the compiler when needed.
+The same file also contains many examples for the users that are interested in +making their own containers available to the meta system. + +When a specialization of the `meta_sequence_container_traits` class exists, the +meta system treats the wrapped type as a sequence container. In a similar way, +a type is treated as an associative container if a specialization of the +`meta_associative_container_traits` class is found for it.
+Proxy objects are returned by dedicated members of the `meta_any` class. The +following is a deliberately verbose example of how users can access a proxy +object for a sequence container: + +```cpp +std::vector vec{1, 2, 3}; +entt::meta_any any = entt::forward_as_meta(vec); + +if(any.type().is_sequence_container()) { + if(auto view = any.as_sequence_container(); view) { + // ... + } +} +``` -* _Meta functions_. They are accessed by name: +The method to use to get a proxy object for associative containers is +`as_associative_container` instead.
+It goes without saying that it's not necessary to perform a double check. +Instead, it's sufficient to query the meta type or verify that the proxy object +is valid. In fact, proxies are contextually convertible to bool to know if they +are valid. For example, invalid proxies are returned when the wrapped object +isn't a container.
+In all cases, users aren't expected to _reflect_ containers explicitly. It's +sufficient to assign a container for which a specialization of the traits +classes exists to a `meta_any` object to be able to get its proxy object. + +The interface of the `meta_sequence_container` proxy object is the same for all +types of sequence containers, although the available features differ from case +to case. In particular: + +* The `value_type` member function returns the meta type of the elements. + +* The `size` member function returns the number of elements in the container as + an unsigned integer value: ```cpp - auto func = entt::resolve().func("member"); + const auto size = view.size(); ``` - The returned type is `meta_func` and may be invalid if there is no meta - function object associated with the given name.
- A meta function object offers an API to query the underlying type (ie to know - if it's a const or a static function), to know the number of arguments, the - meta return type and the meta types of the parameters. In addition, a meta - function object can be used to invoke the underlying function and then get the - return value in the form of meta any object. +* The `resize` member function allows to resize the wrapped container and + returns true in case of success: + + ```cpp + const bool ok = view.resize(3u); + ``` + For example, it's not possible to resize fixed size containers. -* _Meta bases_. They are accessed through the name of the base types: +* The `clear` member function allows to clear the wrapped container and returns + true in case of success: ```cpp - auto base = entt::resolve().base("base"); + const bool ok = view.clear(); ``` - The returned type is `meta_base` and may be invalid if there is no meta base - object associated with the given name.
- Meta bases aren't meant to be used directly, even though they are freely - accessible. They expose only a few methods to use to know the meta type of the - base class and to convert a raw pointer between types. + For example, it's not possible to clear fixed size containers. -* _Meta conversion functions_. They are accessed by type: +* The `begin` and `end` member functions return opaque iterators that can be + used to iterate the container directly: ```cpp - auto conv = entt::resolve().conv(); + for(entt::meta_any element: view) { + // ... + } ``` - The returned type is `meta_conv` and may be invalid if there is no meta - conversion function associated with the given type.
- The meta conversion functions are as thin as the meta bases and with a very - similar interface. The sole difference is that they return a newly created - instance wrapped in a meta any object when they convert between different - types. + In all cases, given an underlying container of type `C`, the returned element + contains an object of type `C::value_type` which therefore depends on the + actual container.
+ All meta iterators are input iterators and don't offer an indirection operator + on purpose. -All the objects thus obtained as well as the meta types can be explicitly -converted to a boolean value to check if they are valid: +* The `insert` member function can be used to add elements to the container. It + accepts a meta iterator and the element to insert: + + ```cpp + auto last = view.end(); + // appends an integer to the container + view.insert(last, 42); + ``` + + This function returns a meta iterator pointing to the inserted element and a + boolean value to indicate whether the operation was successful or not. Note + that a call to `insert` may silently fail in case of fixed size containers or + whether the arguments aren't at least convertible to the required types.
+ Since the meta iterators are contextually convertible to bool, users can rely + on them to know if the operation has failed on the actual container or + upstream, for example for an argument conversion problem. + +* The `erase` member function can be used to remove elements from the container. + It accepts a meta iterator to the element to remove: + + ```cpp + auto first = view.begin(); + // removes the first element from the container + view.erase(first); + ``` + + This function returns a meta iterator following the last removed element and a + boolean value to indicate whether the operation was successful or not. Note + that a call to `erase` may silently fail in case of fixed size containers. + +* The `operator[]` can be used to access elements in a container. It accepts a + single argument, that is the position of the element to return: + + ```cpp + for(std::size_t pos{}, last = view.size(); pos < last; ++pos) { + entt::meta_any value = view[pos]; + // ... + } + ``` + + The function returns instances of `meta_any` that directly refer to the actual + elements. Modifying the returned object will then directly modify the element + inside the container. + +Similarly, also the interface of the `meta_associative_container` proxy object +is the same for all types of associative containers. However, there are some +differences in behavior in the case of key-only containers. In particular: + +* The `key_only` member function returns true if the wrapped container is a + key-only one. + +* The `key_type` member function returns the meta type of the keys. + +* The `mapped_type` member function returns an invalid meta type for key-only + containers and the meta type of the mapped values for all other types of + containers. + +* The `value_type` member function returns the meta type of the elements.
+ For example, it returns the meta type of `int` for `std::set` while it + returns the meta type of `std::pair` for + `std::map`. + +* The `size` member function returns the number of elements in the container as + an unsigned integer value: + + ```cpp + const auto size = view.size(); + ``` + +* The `clear` member function allows to clear the wrapped container and returns + true in case of success: + + ```cpp + const bool ok = view.clear(); + ``` + +* The `begin` and `end` member functions return opaque iterators that can be + used to iterate the container directly: + + ```cpp + for(std::pair element: view) { + // ... + } + ``` + + In all cases, given an underlying container of type `C`, the returned element + is a key-value pair where the key has type `C::key_type` and the value has + type `C::mapped_type`. Since key-only containers don't have a mapped type, + their _value_ is nothing more than an invalid `meta_any` object.
+ All meta iterators are input iterators and don't offer an indirection operator + on purpose. + + While the accessed key is usually constant in the associative containers and + is therefore returned by copy, the value (if any) is wrapped by an instance of + `meta_any` that directly refers to the actual element. Modifying it will then + directly modify the element inside the container. + +* The `insert` member function can be used to add elements to the container. It + accepts two arguments, respectively the key and the value to be inserted: + + ```cpp + auto last = view.end(); + // appends an integer to the container + view.insert(last.handle(), 42, 'c'); + ``` + + This function returns a boolean value to indicate whether the operation was + successful or not. Note that a call to `insert` may fail when the arguments + aren't at least convertible to the required types. + +* The `erase` member function can be used to remove elements from the container. + It accepts a single argument, that is the key to be removed: + + ```cpp + view.erase(42); + ``` + + This function returns a boolean value to indicate whether the operation was + successful or not. Note that a call to `erase` may fail when the argument + isn't at least convertible to the required type. + +* The `operator[]` can be used to access elements in a container. It accepts a + single argument, that is the key of the element to return: + + ```cpp + entt::meta_any value = view[42]; + ``` + + The function returns instances of `meta_any` that directly refer to the actual + elements. Modifying the returned object will then directly modify the element + inside the container. + +Container support is minimal but likely sufficient to satisfy all needs. + +## Pointer-like types + +As with containers, it's also possible to communicate to the meta system which +types to consider _pointers_. This will allow to dereference instances of +`meta_any`, thus obtaining light _references_ to the pointed objects that are +also correctly associated with their meta types.
+To make the meta system recognize a type as _pointer-like_, users can specialize +the `is_meta_pointer_like` class. `EnTT` already exports the specializations for +some common classes. In particular: + +* All types of raw pointers. +* `std::unique_ptr` and `std::shared_ptr`. + +It's important to include the header file `pointer.hpp` to make these +specializations available to the compiler when needed.
+The same file also contains many examples for the users that are interested in +making their own pointer-like types available to the meta system. + +When a type is recognized as a pointer-like one by the meta system, it's +possible to dereference the instances of `meta_any` that contain these objects. +The following is a deliberately verbose example to show how to use this feature: ```cpp -auto func = entt::resolve().func("member"); +int value = 42; +// meta type equivalent to that of int * +entt::meta_any any{&value}; + +if(any.type().is_pointer_like()) { + // meta type equivalent to that of int + if(entt::meta_any ref = *any; ref) { + // ... + } +} +``` -if(func) { - // ... +Of course, it's not necessary to perform a double check. Instead, it's enough to +query the meta type or verify that the returned object is valid. For example, +invalid instances are returned when the wrapped object isn't a pointer-like +type.
+Note that dereferencing a pointer-like object returns an instance of `meta_any` +which refers to the pointed object and allows users to modify it directly +(unless the returned element is const, of course). + +In general, _dereferencing_ a pointer-like type boils down to a `*ptr`. However, +`EnTT` also supports classes that don't offer an `operator*`. In particular: + +* It's possible to exploit a solution based on ADL lookup by offering a function + (also a template one) named `dereference_meta_pointer_like`: + + ```cpp + template + Type & dereference_meta_pointer_like(const custom_pointer_type &ptr) { + return ptr.deref(); + } + ``` + +* When not in control of the type's namespace, it's possible to inject into the + `entt` namespace a specialization of the `adl_meta_pointer_like` class + template to bypass the adl lookup as a whole: + + ```cpp + template + struct entt::adl_meta_pointer_like> { + static decltype(auto) dereference(const custom_pointer_type &ptr) { + return ptr.deref(); + } + }; + ``` + +In all other cases, that is, when dereferencing a pointer works as expected and +regardless of the pointed type, no user intervention is required. + +## Template information + +Meta types also provide a minimal set of information about the nature of the +original type in case it's a class template.
+By default, this works out of the box and requires no user action. However, it's +important to include the header file `template.hpp` to make these information +available to the compiler when needed. + +Meta template information are easily found: + +```cpp +// this method returns true if the type is recognized as a class template specialization +if(auto type = entt::resolve>(); type.is_template_specialization()) { + // meta type of the class template conveniently wrapped by entt::meta_class_template_tag + auto class_type = type.template_type(); + + // number of template arguments + std::size_t arity = type.template_arity(); + + // meta type of the i-th argument + auto arg_type = type.template_arg(0u); } ``` -Furthermore, all meta objects with the exception of meta destructors can be -iterated through an overload that accepts a callback through which to return -them. As an example: +Typically, when template information for a type are required, what the library +provides is sufficient. However, there are some cases where a user may want more +details or a different set of information.
+Consider the case of a class template that is meant to wrap function types: + +```cpp +template +struct function_type; + +template +struct function_type {}; +``` + +In this case, rather than the function type, the user might want the return type +and unpacked arguments as if they were different template parameters for the +original class template.
+To achieve this, users must enter the library internals and provide their own +specialization for the class template `entt::meta_template_traits`, such as: + +```cpp +template +struct entt::meta_template_traits> { + using class_type = meta_class_template_tag; + using args_type = type_list; +}; +``` + +The reflection system doesn't verify the accuracy of the information nor infer a +correspondence between real types and meta types.
+Therefore, the specialization will be used as is and the information it contains +will be associated with the appropriate type when required. + +## Automatic conversions + +In C++, there are a number of conversions allowed between arithmetic types that +make it convenient to work with this kind of data.
+If this were to be translated into explicit registrations with the reflection +system, it would result in a long series of instructions such as the following: ```cpp -entt::resolve().data([](auto data) { +entt::meta() + .conv() + .conv() // ... -}); + .conv(); ``` -A meta type can also be used to `construct` or `destroy` actual instances of the -underlying type.
-In particular, the `construct` member function accepts a variable number of -arguments and searches for a match. It returns a `meta_any` object that may or -may not be initialized, depending on whether a suitable constructor has been -found or not. On the other side, the `destroy` member function accepts instances -of `meta_any` as well as actual objects by reference and invokes the registered -destructor if any or a default one.
-Be aware that the result of a call to `destroy` may not be what is expected. +Repeated for each type eligible to undergo this type of conversions. This is +both error prone and repetitive.
+Similarly, the language allows users to silently convert unscoped enums to their +underlying types and offers what it takes to do the same for scoped enums. It +would result in the following if it were to be done explicitly: -Meta types and meta objects in general contain much more than what is said: a -plethora of functions in addition to those listed whose purposes and uses go -unfortunately beyond the scope of this document.
-I invite anyone interested in the subject to look at the code, experiment and -read the official documentation to get the best out of this powerful tool. +```cpp +entt::meta() + .conv>(); +``` + +Fortunately, all of this can also be avoided. `EnTT` offers implicit support for +these types of conversions: + +```cpp +entt::meta_any any{42}; +any.allow_cast(); +double value = any.cast(); +``` -# Named constants and enums +With no need for registration, the conversion takes place automatically under +the hood. The same goes for a call to `allow_cast` involving a meta type: + +```cpp +entt::meta_type type = entt::resolve(); +entt::meta_any any{my_enum::a_value}; +any.allow_cast(type); +int value = any.cast(); +``` + +This should make working with arithmetic types and scoped or unscoped enums as +easy as it is in C++.
+It's also worth noting that it's still possible to set up conversion functions +manually and these will always be preferred over the automatic ones. + +## Implicitly generated default constructor + +In many cases, it's useful to be able to create objects of default constructible +types through the reflection system, while not having to explicitly register the +meta type or the default constructor.
+For example, in the case of primitive types like `int` or `char`, but not just +them. + +For this reason and only for default constructible types, default constructors +are automatically defined and associated with their meta types, whether they are +explicitly or implicitly generated.
+Therefore, this is all is needed to construct an integer from its meta type: + +```cpp +entt::resolve().construct(); +``` + +Where the meta type can be for example the one returned from a meta container, +useful for building keys without knowing or having to register the actual types. + +In all cases, when users register default constructors, they are preferred both +during searches and when the `construct` member function is invoked. + +## Policies: the more, the less + +Policies are a kind of compile-time directives that can be used when registering +reflection information.
+Their purpose is to require slightly different behavior than the default in some +specific cases. For example, when reading a given data member, its value is +returned wrapped in a `meta_any` object which, by default, makes a copy of it. +For large objects or if the caller wants to access the original instance, this +behavior isn't desirable. Policies are there to offer a solution to this and +other problems. + +There are a few alternatives available at the moment: + +* The _as-is_ policy, associated with the type `entt::as_is_t`.
+ This is the default policy. In general, it should never be used explicitly, + since it's implicitly selected if no other policy is specified.
+ In this case, the return values of the functions as well as the properties + exposed as data members are always returned by copy in a dedicated wrapper and + therefore associated with their original meta types. + +* The _as-void_ policy, associated with the type `entt::as_void_t`.
+ Its purpose is to discard the return value of a meta object, whatever it is, + thus making it appear as if its type were `void`: + + ```cpp + entt::meta().func<&my_type::member_function, entt::as_void_t>("member"_hs); + ``` + + If the use with functions is obvious, it must be said that it's also possible + to use this policy with constructors and data members. In the first case, the + constructor will be invoked but the returned wrapper will actually be empty. + In the second case, instead, the property will not be accessible for reading. + +* The _as-ref_ and _as-cref_ policies, associated with the types + `entt::as_ref_t` and `entt::as_cref_t`.
+ They allow to build wrappers that act as references to unmanaged objects. + Accessing the object contained in the wrapper for which the _reference_ was + requested will make it possible to directly access the instance used to + initialize the wrapper itself: + + ```cpp + entt::meta().data<&my_type::data_member, entt::as_ref_t>("member"_hs); + ``` + + These policies work with constructors (for example, when objects are taken + from an external container rather than created on demand), data members and + functions in general.
+ If on the one hand `as_cref_t` always forces the return type to be const, + `as_ref_t` _adapts_ to the constness of the passed object and to that of the + return type if any. + +Some uses are rather trivial, but it's useful to note that there are some less +obvious corner cases that can in turn be solved with the use of policies. + +## Named constants and enums A special mention should be made for constant values and enums. It wouldn't be necessary, but it will help distracted readers. @@ -342,48 +856,86 @@ members of the reflected types. Exporting constant values or elements from an enum is as simple as ever: ```cpp -entt::reflect() - .data("a_value") - .data("another_value"); +entt::meta() + .data("a_value"_hs) + .data("another_value"_hs); -entt::reflect().data<2048>("max_int"); +entt::meta().data<2048>("max_int"_hs); ``` It goes without saying that accessing them is trivial as well. It's a matter of doing the following, as with any other data member of a meta type: ```cpp -auto value = entt::resolve().data("a_value").get({}).cast(); -auto max = entt::resolve().data("max_int").get({}).cast(); +auto value = entt::resolve().data("a_value"_hs).get({}).cast(); +auto max = entt::resolve().data("max_int"_hs).get({}).cast(); ``` As a side note, remember that all this happens behind the scenes without any -allocation because of the small object optimization performed by the meta any +allocation because of the small object optimization performed by the `meta_any` class. -# Properties and meta objects +## Properties and meta objects -Sometimes (ie when it comes to creating an editor) it might be useful to be able +Sometimes (for example, when it comes to creating an editor) it might be useful to attach properties to the meta objects created. Fortunately, this is possible for most of them.
-To attach a property to a meta object, no matter what as long as it supports -properties, it is sufficient to provide an object at the time of construction -such that `std::get<0>` and `std::get<1>` are valid for it. In other terms, the -properties are nothing more than key/value pairs users can put in an -`std::pair`. As an example: +For the meta objects that support properties, the member functions of the +factory used for registering them will return a decorated version of the factory +itself. The latter can be used to attach properties to the last created meta +object.
+Apparently, it's more difficult to say than to do: ```cpp -entt::reflect("reflected", std::make_pair("tooltip"_hs, "message")); +entt::meta().type("reflected_type"_hs).prop("tooltip"_hs, "message"); ``` -The meta objects that support properties offer then a couple of member functions -named `prop` to iterate them at once and to search a specific property by key: +Properties are always in the key/value form. There are no restrictions on the +type of the key or value, as long as they are copy constructible objects.
+Multiple formats are supported when it comes to defining a property: + +* Properties as key/value pairs: + + ```cpp + entt::meta().type("reflected_type"_hs).prop("tooltip"_hs, "message"); + ``` + +* Properties as `std::pair`s: + + ```cpp + entt::meta().type("reflected_type"_hs).prop(std::make_pair("tooltip"_hs, "message")); + ``` + +* Key only properties: + + ```cpp + entt::meta().type("reflected_type"_hs).prop(my_enum::key_only); + ``` + +* Properties as `std::tuple`s: + + ```cpp + entt::meta().type("reflected_type"_hs).prop(std::make_tuple(std::make_pair("tooltip"_hs, "message"), my_enum::key_only)); + ``` + + A tuple contains one or more properties. All of them are treated individually. + +Note that it's not possible to invoke `prop` multiple times for the same meta +object and trying to do that will result in a compilation error.
+However, the `props` function is available to associate several properties at +once. In this case, properties in the key/value form aren't allowed, since they +would be interpreted as two different properties rather than a single one. + +The meta objects for which properties are supported are currently meta types, +meta data and meta functions.
+These types also offer a couple of member functions named `prop` to iterate all +properties at once or to search a specific property by key: ```cpp -// iterate all the properties of a meta type -entt::resolve().prop([](auto prop) { +// iterate all properties of a meta type +for(auto prop: entt::resolve().prop()) { // ... -}); +} // search for a given property by name auto prop = entt::resolve().prop("tooltip"_hs); @@ -391,24 +943,36 @@ auto prop = entt::resolve().prop("tooltip"_hs); Meta properties are objects having a fairly poor interface, all in all. They only provide the `key` and the `value` member functions to be used to retrieve -the key and the value contained in the form of meta any objects, respectively. +the key and the value contained in the form of `meta_any` objects, respectively. -# Unregister types +## Unregister types A type registered with the reflection system can also be unregistered. This means unregistering all its data members, member functions, conversion functions -and so on. However, the base classes won't be unregistered, since they don't +and so on. However, base classes aren't unregistered as well, since they don't necessarily depend on it. Similarly, implicitly generated types (as an example, -the meta types implicitly generated for function parameters when needed) won't -be unregistered. +the meta types implicitly generated for function parameters when needed) aren't +unregistered.
+Roughly speaking, unregistering a type means disconnecting all associated meta +objects from it and making its identifier no longer visible. The underlying node +will remain available though, as if it were implicitly generated: + +```cpp +entt::meta_reset(); +``` + +It's also possible to reset types by their unique identifiers if required: + +```cpp +entt::meta_reset("my_type"_hs); +``` -To unregister a type, users can use the `unregister` function from the global -namespace: +Finally, there exists a non-template overload of the `meta_reset` function that +doesn't accept argument and resets all searchable types (that is, all types that +were assigned an unique identifier): ```cpp -entt::unregister(); +entt::meta_reset(); ``` -This function returns a boolean value that is true if the type is actually -registered with the reflection system, false otherwise.
-The type can be re-registered later with a completely different name and form. +All types can be re-registered later with a completely different name and form. diff --git a/modules/entt/docs/md/poly.md b/modules/entt/docs/md/poly.md new file mode 100644 index 0000000..c87804e --- /dev/null +++ b/modules/entt/docs/md/poly.md @@ -0,0 +1,359 @@ +# Crash Course: poly + + +# Table of Contents + +* [Introduction](#introduction) + * [Other libraries](#other-libraries) +* [Concept and implementation](#concept-and-implementation) + * [Deduced interface](#deduced-interface) + * [Defined interface](#defined-interface) + * [Fulfill a concept](#fulfill-a-concept) +* [Inheritance](#inheritance) +* [Static polymorphism in the wild](#static-polymorphism-in-the-wild) +* [Storage size and alignment requirement](#storage-size-and-alignment-requirement) + + +# Introduction + +Static polymorphism is a very powerful tool in C++, albeit sometimes cumbersome +to obtain.
+This module aims to make it simple and easy to use. + +The library allows to define _concepts_ as interfaces to fulfill with concrete +classes without having to inherit from a common base.
+This is, among others, one of the advantages of static polymorphism in general +and of a generic wrapper like that offered by the `poly` class template in +particular.
+What users get is an object that can be passed around as such and not through a +reference or a pointer, as happens when it comes to working with dynamic +polymorphism. + +Since the `poly` class template makes use of `entt::any` internally, it also +supports most of its feature. Among the most important, the possibility to +create aliases to existing and thus unmanaged objects. This allows users to +exploit the static polymorphism while maintaining ownership of objects.
+Likewise, the `poly` class template also benefits from the small buffer +optimization offered by the `entt::any` class and therefore minimizes the number +of allocations, avoiding them altogether where possible. + +## Other libraries + +There are some very interesting libraries regarding static polymorphism.
+Among all, the two that I prefer are: + +* [`dyno`](https://github.com/ldionne/dyno): runtime polymorphism done right. +* [`Poly`](https://github.com/facebook/folly/blob/master/folly/docs/Poly.md): + a class template that makes it easy to define a type-erasing polymorphic + object wrapper. + +The former is admittedly an experimental library, with many interesting ideas. +I've some doubts about the usefulness of some feature in real world projects, +but perhaps my lack of experience comes into play here. In my opinion, its only +flaw is the API which I find slightly more cumbersome than other solutions.
+The latter was undoubtedly a source of inspiration for this module, although I +opted for different choices in the implementation of both the final API and some +feature. + +Either way, the authors are gurus of the C++ community, people I only have to +learn from. + +# Concept and implementation + +The first thing to do to create a _type-erasing polymorphic object wrapper_ (to +use the terminology introduced by Eric Niebler) is to define a _concept_ that +types will have to adhere to.
+For this purpose, the library offers a single class that supports both deduced +and fully defined interfaces. Although having interfaces deduced automatically +is convenient and allows users to write less code in most cases, this has some +limitations and it's therefore useful to be able to get around the deduction by +providing a custom definition for the static virtual table. + +Once the interface is defined, it will be sufficient to provide a generic +implementation to fulfill the concept.
+Also in this case, the library allows customizations based on types or families +of types, so as to be able to go beyond the generic case where necessary. + +## Deduced interface + +This is how a concept with a deduced interface is introduced: + +```cpp +struct Drawable: entt::type_list<> { + template + struct type: Base { + void draw() { this->template invoke<0>(*this); } + }; + + // ... +}; +``` + +It's recognizable by the fact that it inherits from an empty type list.
+Functions can also be const, accept any number of parameters and return a type +other than `void`: + +```cpp +struct Drawable: entt::type_list<> { + template + struct type: Base { + bool draw(int pt) const { return this->template invoke<0>(*this, pt); } + }; + + // ... +}; +``` + +In this case, all parameters must be passed to `invoke` after the reference to +`this` and the return value is whatever the internal call returns.
+As for `invoke`, this is a name that is injected into the _concept_ through +`Base`, from which one must necessarily inherit. Since it's also a dependent +name, the `this-> template` form is unfortunately necessary due to the rules of +the language. However, there exists also an alternative that goes through an +external call: + +```cpp +struct Drawable: entt::type_list<> { + template + struct type: Base { + void draw() const { entt::poly_call<0>(*this); } + }; + + // ... +}; +``` + +Once the _concept_ is defined, users must provide a generic implementation of it +in order to tell the system how any type can satisfy its requirements. This is +done via an alias template within the concept itself.
+The index passed as a template parameter to either `invoke` or `poly_call` +refers to how this alias is defined. + +## Defined interface + +A fully defined concept is no different to one for which the interface is +deduced, with the only difference that the list of types is not empty this time: + +```cpp +struct Drawable: entt::type_list { + template + struct type: Base { + void draw() { entt::poly_call<0>(*this); } + }; + + // ... +}; +``` + +Again, parameters and return values other than `void` are allowed. Also, the +function type must be const when the method to bind to it is const: + +```cpp +struct Drawable: entt::type_list { + template + struct type: Base { + bool draw(int pt) const { return entt::poly_call<0>(*this, pt); } + }; + + // ... +}; +``` + +Why should a user fully define a concept if the function types are the same as +the deduced ones?
+Because, in fact, this is exactly the limitation that can be worked around by +manually defining the static virtual table. + +When things are deduced, there is an implicit constraint.
+If the concept exposes a member function called `draw` with function type +`void()`, a concept can be satisfied: + +* Either by a class that exposes a member function with the same name and the + same signature. + +* Or through a lambda that makes use of existing member functions from the + interface itself. + +In other words, it's not possible to make use of functions not belonging to the +interface, even if they are present in the types that fulfill the concept.
+Similarly, it's not possible to deduce a function in the static virtual table +with a function type different from that of the associated member function in +the interface itself. + +Explicitly defining a static virtual table suppresses the deduction step and +allows maximum flexibility when providing the implementation for a concept. + +## Fulfill a concept + +The `impl` alias template of a concept is used to define how it's fulfilled: + +```cpp +struct Drawable: entt::type_list<> { + // ... + + template + using impl = entt::value_list<&Type::draw>; +}; +``` + +In this case, it's stated that the `draw` method of a generic type will be +enough to satisfy the requirements of the `Drawable` concept.
+Both member functions and free functions are supported to fulfill concepts: + +```cpp +template +void print(Type &self) { self.print(); } + +struct Drawable: entt::type_list { + // ... + + template + using impl = entt::value_list<&print>; +}; +``` + +Likewise, as long as the parameter types and return type support conversions to +and from those of the function type referenced in the static virtual table, the +actual implementation may differ in its function type since it's erased +internally.
+Moreover, the `self` parameter isn't strictly required by the system and can be +left out for free functions if not required. + +Refer to the inline documentation for more details. + +# Inheritance + +_Concept inheritance_ is straightforward due to how poly looks like in `EnTT`. +Therefore, it's quite easy to build hierarchies of concepts if necessary.
+The only constraint is that all concepts in a hierarchy must belong to the same +_family_, that is, they must be either all deduced or all defined. + +For a deduced concept, inheritance is achieved in a few steps: + +```cpp +struct DrawableAndErasable: entt::type_list<> { + template + struct type: typename Drawable::template type { + static constexpr auto base = std::tuple_size_v::type>; + void erase() { entt::poly_call(*this); } + }; + + template + using impl = entt::value_list_cat_t< + typename Drawable::impl, + entt::value_list<&Type::erase> + >; +}; +``` + +The static virtual table is empty and must remain so.
+On the other hand, `type` no longer inherits from `Base` and instead forwards +its template parameter to the type exposed by the _base class_. Internally, the +size of the static virtual table of the base class is used as an offset for the +local indexes.
+Finally, by means of the `value_list_cat_t` utility, the implementation consists +in appending the new functions to the previous list. + +As for a defined concept instead, also the list of types must be extended, in a +similar way to what is shown for the implementation of the above concept.
+To do this, it's useful to declare a function that allows to convert a _concept_ +into its underlying `type_list` object: + +```cpp +template +entt::type_list as_type_list(const entt::type_list &); +``` + +The definition isn't strictly required, since the function will only be used +through a `decltype` as it follows: + +```cpp +struct DrawableAndErasable: entt::type_list_cat_t< + decltype(as_type_list(std::declval())), + entt::type_list +> { + // ... +}; +``` + +Similar to above, `type_list_cat_t` is used to concatenate the underlying static +virtual table with the new function types.
+Everything else is the same as already shown instead. + +# Static polymorphism in the wild + +Once the _concept_ and implementation have been introduced, it will be possible +to use the `poly` class template to contain instances that meet the +requirements: + +```cpp +using drawable = entt::poly; + +struct circle { + void draw() { /* ... */ } +}; + +struct square { + void draw() { /* ... */ } +}; + +// ... + +drawable instance{circle{}}; +instance->draw(); + +instance = square{}; +instance->draw(); +``` + +The `poly` class template offers a wide range of constructors, from the default +one (which will return an uninitialized `poly` object) to the copy and move +constructors, as well as the ability to create objects in-place.
+Among others, there is also a constructor that allows users to wrap unmanaged +objects in a `poly` instance (either const or non-const ones): + +```cpp +circle shape; +drawable instance{std::in_place_type, shape}; +``` + +Similarly, it's possible to create non-owning copies of `poly` from an existing +object: + +```cpp +drawable other = instance.as_ref(); +``` + +In both cases, although the interface of the `poly` object doesn't change, it +won't construct any element or take care of destroying the referenced objects. + +Note also how the underlying concept is accessed via a call to `operator->` and +not directly as `instance.draw()`.
+This allows users to decouple the API of the wrapper from that of the concept. +Therefore, where `instance.data()` will invoke the `data` member function of the +poly object, `instance->data()` will map directly to the functionality exposed +by the underlying concept. + +# Storage size and alignment requirement + +Under the hood, the `poly` class template makes use of `entt::any`. Therefore, +it can take advantage of the possibility of defining at compile-time the size of +the storage suitable for the small buffer optimization as well as the alignment +requirements: + +```cpp +entt::basic_poly +``` + +The default size is `sizeof(double[2])`, which seems like a good compromise +between a buffer that is too large and one unable to hold anything larger than +an integer. The alignment requirement is optional instead and by default such +that it's the most stringent (the largest) for any object whose size is at most +equal to the one provided.
+It's worth noting that providing a size of 0 (which is an accepted value in all +respects) will force the system to dynamically allocate the contained objects in +all cases. diff --git a/modules/entt/docs/md/process.md b/modules/entt/docs/md/process.md index e364517..8c505f8 100644 --- a/modules/entt/docs/md/process.md +++ b/modules/entt/docs/md/process.md @@ -28,9 +28,9 @@ A typical process must inherit from the `process` class template that stays true to the CRTP idiom. Moreover, derived classes must specify what's the intended type for elapsed times. -A process should expose publicly the following member functions whether -required (note that it isn't required to define a function unless the derived -class wants to _override_ the default behavior): +A process should expose publicly the following member functions whether needed +(note that it isn't required to define a function unless the derived class wants +to _override_ the default behavior): * `void update(Delta, void *);` @@ -74,6 +74,10 @@ Here is a minimal example for the sake of curiosity: struct my_process: entt::process { using delta_type = std::uint32_t; + my_process(delta_type delay) + : remaining{delay} + {} + void update(delta_type delta, void *) { remaining -= std::min(remaining, delta); @@ -85,7 +89,7 @@ struct my_process: entt::process { } private: - delta_type remaining{1000u}; + delta_type remaining; }; ``` @@ -158,7 +162,7 @@ To attach a process to a scheduler there are mainly two ways: the `attach` member function: ```cpp - scheduler.attach("foobar"); + scheduler.attach(1000u); ``` * Otherwise, in case of a lambda or a functor, it's enough to provide an @@ -182,7 +186,7 @@ scheduler.attach([](auto delta, void *, auto succeed, auto fail) { // ... }) // appends a child in the form of a process class -.then(); +.then(1000u); ``` To update a scheduler and therefore all its processes, the `update` member diff --git a/modules/entt/docs/md/reference.md b/modules/entt/docs/md/reference.md new file mode 100644 index 0000000..e41b3d9 --- /dev/null +++ b/modules/entt/docs/md/reference.md @@ -0,0 +1,75 @@ +# Similar projects + +There are many projects similar to `EnTT`, both open source and not.
+Some even borrowed some ideas from this library and expressed them in different +languages.
+Others developed different architectures from scratch and therefore offer +alternative solutions with their pros and cons. + +Below an incomplete list of those that I've come across so far.
+If some terms or designs aren't clear, I recommend referring to the +[_ECS Back and Forth_](https://skypjack.github.io/tags/#ecs) series for all the +details. + +I hope this list can grow much more in the future: + +* C: + * [destral_ecs](https://github.com/roig/destral_ecs): a single-file ECS based + on sparse sets. + * [Diana](https://github.com/discoloda/Diana): an ECS that uses sparse sets to + keep track of entities in systems. + * [Flecs](https://github.com/SanderMertens/flecs): a multithreaded archetype + ECS based on semi-contiguous arrays rather than chunks. + * [lent](https://github.com/nem0/lent): the Donald Trump of the ECS libraries. + +* C++: + * [decs](https://github.com/vblanco20-1/decs): a chunk based archetype ECS. + * [ecst](https://github.com/SuperV1234/ecst): a multithreaded compile-time + ECS that uses sparse sets to keep track of entities in systems. + * [EntityX](https://github.com/alecthomas/entityx): a bitset based ECS that + uses a single large matrix of components indexed with entities. + * [Gaia-ECS](https://github.com/richardbiely/gaia-ecs): a chunk based + archetype ECS. + * [Polypropylene](https://github.com/pmbittner/Polypropylene): a hybrid + solution between an ECS and dynamic mixins. + +* C# + * [Entitas](https://github.com/sschmid/Entitas-CSharp): the ECS framework for + C# and Unity, where _reactive systems_ were invented. + * [LeoECS](https://github.com/Leopotam/ecs): simple lightweight C# Entity + Component System framework. + * [Svelto.ECS](https://github.com/sebas77/Svelto.ECS): a very interesting + platform agnostic and table based ECS framework. + +* Go: + * [gecs](https://github.com/tutumagi/gecs): a sparse sets based ECS inspired + by `EnTT`. + +* Javascript: + * [\@javelin/ecs](https://github.com/3mcd/javelin/tree/master/packages/ecs): + an archetype ECS in TypeScript. + * [ecsy](https://github.com/MozillaReality/ecsy): I haven't had the time to + investigate the underlying design of `ecsy` but it looks cool anyway. + +* Perl: + * [Game::Entities](https://gitlab.com/jjatria/perl-game-entities): a simple + entity registry for ECS designs inspired by `EnTT`. + +* Raku: + * [Game::Entities](https://gitlab.com/jjatria/raku-game-entities): a simple + entity registry for ECS designs inspired by `EnTT`. + +* Rust: + * [Legion](https://github.com/TomGillen/legion): a chunk based archetype ECS. + * [Shipyard](https://github.com/leudz/shipyard): it borrows some ideas from + `EnTT` and offers a sparse sets based ECS with grouping functionalities. + * [Sparsey](https://github.com/LechintanTudor/sparsey): sparse set based ECS + written in Rust. + * [Specs](https://github.com/amethyst/specs): a parallel ECS based mainly on + hierarchical bitsets that allows different types of storage as needed. + +* Zig + * [zig-ecs](https://github.com/prime31/zig-ecs): a _zig-ification_ of `EnTT`. + +If you know of other resources out there that can be of interest for the reader, +feel free to open an issue or a PR and I'll be glad to add them to this page. diff --git a/modules/entt/docs/md/resource.md b/modules/entt/docs/md/resource.md index 0c3e5fd..6dd3fff 100644 --- a/modules/entt/docs/md/resource.md +++ b/modules/entt/docs/md/resource.md @@ -7,6 +7,9 @@ * [Introduction](#introduction) * [The resource, the loader and the cache](#the-resource-the-loader-and-the-cache) + * [Resource handle](#resource-handle) + * [Loaders](#loader) + * [The cache class](#the-cache) @@ -21,218 +24,169 @@ Examples are loading everything on start, loading on request, predictive loading, and so on. `EnTT` doesn't pretend to offer a _one-fits-all_ solution for the different -cases. Instead, it offers a minimal and perhaps trivial cache that can be useful -most of the time during prototyping and sometimes even in a production -environment.
-For those interested in the subject, the plan is to improve it considerably over -time in terms of performance, memory usage and functionalities. Hoping to make -it, of course, one step at a time. +cases.
+Instead, the library offers a minimal, general purpose resource cache that might +be useful in many cases. # The resource, the loader and the cache -There are three main actors in the model: the resource, the loader and the -cache. - -The _resource_ is whatever users want it to be. An image, a video, an audio, -whatever. There are no limits.
-As a minimal example: +Resource, loader and cache are the three main actors for the purpose.
+The _resource_ is an image, an audio, a video or any other type: ```cpp struct my_resource { const int value; }; ``` -A _loader_ is a class the aim of which is to load a specific resource. It has to -inherit directly from the dedicated base class as in the following example: +The _loader_ is a callable type the aim of which is to load a specific resource: ```cpp -struct my_loader final: entt::resource_loader { - // ... -}; -``` - -Where `my_resource` is the type of resources it creates.
-A resource loader must also expose a public const member function named `load` -that accepts a variable number of arguments and returns a shared pointer to a -resource.
-As an example: +struct my_loader final { + using result_type = std::shared_ptr; -```cpp -struct my_loader: entt::resource_loader { - std::shared_ptr load(int value) const { + result_type operator()(int value) const { // ... - return std::shared_ptr(new my_resource{ value }); + return std::make_shared(value); } }; ``` -In general, resource loaders should not have a state or retain data of any type. -They should let the cache manage their resources instead.
-As a side note, base class and CRTP idiom aren't strictly required with the -current implementation. One could argue that a cache can easily work with -loaders of any type. However, future changes won't be breaking ones by forcing -the use of a base class today and that's why the model is already in its place. +Its function operator can accept any arguments and should return a value of the +declared result type (`std::shared_ptr` in the example).
+A loader can also overload its function call operator to make it possible to +construct the same or another resource from different lists of arguments. Finally, a cache is a specialization of a class template tailored to a specific -resource: +resource and (optionally) a loader: ```cpp -using my_resource_cache = entt::resource_cache; +using my_cache = entt::resource_cache; // ... -my_resource_cache cache{}; +my_cache cache{}; ``` -The idea is to create different caches for different types of resources and to -manage each one independently in the most appropriate way.
+The cache is meant to be used to create different caches for different types of +resources and to manage each one independently in the most appropriate way.
As a (very) trivial example, audio tracks can survive in most of the scenes of -an application while meshes can be associated with a single scene and then -discarded when users leave it. +an application while meshes can be associated with a single scene only, then +discarded when a player leaves it. -A cache offers a set of basic functionalities to query its internal state and to -_organize_ it: +## Resource handle -```cpp -// gets the number of resources managed by a cache -const auto size = cache.size(); +Resources aren't returned directly to the caller. Instead, they are wrapped in a +_resource handle_ identified by the `entt::resource` class template.
+For those who know the _flyweight design pattern_ already, that's exactly what +it is. To all others, this is the time to brush up on some notions instead. -// checks if a cache contains at least a valid resource -const auto empty = cache.empty(); +A shared pointer could have been used as a resource handle. In fact, the default +handle mostly maps the interface of its standard counterpart and only adds a few +things to it.
+However, the handle in `EnTT` is designed as a standalone class template named +`resource`. It boils down to the fact that specializing a class in the standard +is often undefined behavior while having the ability to specialize the handle +for one, more or all resource types could help over time. -// clears a cache and discards its content -cache.clear(); -``` +## Loaders -Besides these member functions, a cache contains what is needed to load, use and -discard resources of the given type.
-Before to explore this part of the interface, it makes sense to mention how -resources are identified. The type of the identifiers to use is defined as: +A loader is a class that is responsible for _loading_ the resources.
+By default, it's just a callable object which forwards its arguments to the +resource itself. That is, a _passthrough type_. All the work is demanded to the +constructor(s) of the resource itself.
+Loaders also are fully customizable as expected. -```cpp -entt::resource_cache::resource_type -``` +A custom loader is a class with at least one function call operator and a member +type named `result_type`.
+The loader isn't required to return a resource handle. As long as `return_type` +is suitable for constructing a handle, that's fine. -Where `resource_type` is an alias for `entt::hashed_string::hash_type`. -Therefore, resource identifiers are created explicitly as in the following -example: +When using the default handle, it expects a resource type which is convertible +to or suitable for constructing an `std::shared_ptr` (where `Type` is the +actual resource type).
+In other terms, the loader should return shared pointers to the given resource +type. However, it isn't mandatory. Users can easily get around this constraint +by specializing both the handle and the loader. -```cpp -constexpr auto identifier = entt::resource_cache::resource_type{"my/resource/identifier"_hs}; -// this is equivalent to the following -constexpr auto hs = entt::hashed_string{"my/resource/identifier"}; -``` - -The class `hashed_string` is described in a dedicated section, so I won't go in -details here. - -Resources are loaded and thus stored in a cache through the `load` member -function. It accepts the loader to use as a template parameter, the resource -identifier and the parameters used to construct the resource as arguments: +A cache forwards all its arguments to the loader if required. This means that +loaders can also support tag dispatching to offer different loading policies: ```cpp -// uses the identifier declared above -cache.load(identifier, 0); +struct my_loader { + using result_type = std::shared_ptr; -// uses a const char * directly as an identifier -cache.load("another/identifier"_hs, 42); -``` + struct from_disk_tag{}; + struct from_network_tag{}; -The function returns a handle to the resource, whether it already exists or is -loaded. In case the loader returns an invalid pointer, the handle is invalid as -well and therefore it can be easily used with an `if` statement: + template + result_type operator()(from_disk_tag, Args&&... args) { + // ... + return std::make_shared(std::forward(args)...); + } -```cpp -if(auto handle = cache.load("another/identifier"_hs, 42); handle) { - // ... + template + result_type operator()(from_network_tag, Args&&... args) { + // ... + return std::make_shared(std::forward(args)...); + } } ``` -Before trying to load a resource, the `contains` member function can be used to -know if a cache already contains a specific resource: +This makes the whole loading logic quite flexible and easy to extend over time. -```cpp -auto exists = cache.contains("my/identifier"_hs); -``` +## The cache class -There exists also a member function to use to force a reload of an already -existing resource if needed: +The cache is the class that is asked to _connect the dots_.
+It loads the resources, store them aside and returns handles as needed: ```cpp -auto handle = cache.reload("another/identifier"_hs, 42); +entt::resource_cache cache{}; ``` -As above, the function returns a handle to the resource that is invalid in case -of errors. The `reload` member function is a kind of alias of the following -snippet: +Under the hood, a cache is nothing more than a map where the key value has type +`entt::id_type` while the mapped value is whatever type its loader returns.
+For this reason, it offers most of the functionality a user would expect from a +map, such as `empty` or `size` and so on. Similarly, it's an iterable type that +also supports indexing by resource id: ```cpp -cache.discard(identifier); -cache.load(identifier, 42); -``` - -Where the `discard` member function is used to get rid of a resource if loaded. -In case the cache doesn't contain a resource for the given identifier, `discard` -does nothing and returns immediately. - -So far, so good. Resources are finally loaded and stored within the cache.
-They are returned to users in the form of handles. To get one of them later on: +for(entt::resource curr: cache) { + // ... +} -```cpp -auto handle = cache.handle("my/identifier"_hs); +if(entt::resource res = cache["resource/id"_hs]; res) { + // ... +} ``` -The idea behind a handle is the same of the flyweight pattern. In other terms, -resources aren't copied around. Instead, instances are shared between handles. -Users of a resource own a handle that guarantees that a resource isn't destroyed -until all the handles are destroyed, even if the resource itself is removed from -the cache.
-Handles are tiny objects both movable and copyable. They return the contained -resource as a const reference on request: - -* By means of the `get` member function: - - ```cpp - const auto &resource = handle.get(); - ``` - -* Using the proper cast operator: - - ```cpp - const auto &resource = handle; - ``` +Please, refer to the inline documentation for all the details about the other +functions (for example `contains` or `erase`). -* Through the dereference operator: - - ```cpp - const auto &resource = *handle; - ``` - -The resource can also be accessed directly using the arrow operator if required: +Set aside the part of the API that this class shares with a map, it also adds +something on top of it in order to address the most common requirements of a +resource cache.
+In particular, it doesn't have an `emplace` member function which is replaced by +`load` and `force_load` instead (where the former loads a new resource only if +not present while the second triggers a forced loading in any case): ```cpp -auto value = handle->value; -``` +auto ret = cache.load("resource/id"_hs); -To test if a handle is still valid, the cast operator to `bool` allows users to -use it in a guard: +// true only if the resource was not already present +const bool loaded = ret.second; -```cpp -if(handle) { - // ... -} +// takes the resource handle pointed to by the returned iterator +entt::resource res = *ret.first; ``` -Finally, in case there is the need to load a resource and thus to get a handle -without storing the resource itself in the cache, users can rely on the `temp` -member function template.
-The declaration is similar to that of `load`, a (possibly invalid) handle for -the resource is returned also in this case: - -```cpp -if(auto handle = cache.temp(42); handle) { - // ... -} -``` +Note that the hashed string is used for convenience in the example above.
+Resource identifiers are nothing more than integral values. Therefore, plain +numbers as well as non-class enum value are accepted. -Do not forget to test the handle for validity. Otherwise, getting a reference to -the resource it points may result in undefined behavior. +Moreover, it's worth mentioning that both the iterators of a cache and its +indexing operators return resource handles rather than instances of the mapped +type.
+Since the cache has no control over the loader and a resource isn't required to +also be convertible to bool, these handles can be invalid. This usually means an +error in the user logic but it may also be an _expected_ event.
+It's therefore recommended to verify handles validity with a check in debug (for +example, when loading) or an appropriate logic in retail. diff --git a/modules/entt/docs/md/signal.md b/modules/entt/docs/md/signal.md index 1a2fbdf..1491ca4 100644 --- a/modules/entt/docs/md/signal.md +++ b/modules/entt/docs/md/signal.md @@ -7,8 +7,11 @@ * [Introduction](#introduction) * [Delegate](#delegate) + * [Runtime arguments](#runtime-arguments) + * [Lambda support](#lambda-support) * [Signals](#signals) * [Event dispatcher](#event-dispatcher) + * [Named queues](#named-queues) * [Event emitter](#event-emitter) +# Table of Contents + +* [Enable Cpp17](#enable-cpp17) +* [EnTT as a third party module](#entt-as-a-third-party-module) +* [Include EnTT](#include-entt) + + +## Enable Cpp17 + +As of writing (Unreal Engine v4.25), the default C++ standard of Unreal Engine +is C++14.
+On the other hand, note that `EnTT` requires C++17 to compile. To enable it, in +the main module of the project there should be a `.Build.cs` file, +the constructor of which must contain the following lines: + +```cs +PCHUsage = PCHUsageMode.NoSharedPCHs; +PrivatePCHHeaderFile = ".h"; +CppStandard = CppStandardVersion.Cpp17; +``` + +Replace `.h` with the name of the already existing PCH header +file, if any.
+In case the project doesn't already contain a file of this type, it's possible +to create one with the following content: + +```cpp +#pragma once +#include "CoreMinimal.h" +``` + +Remember to remove any old `PCHUsage = <...>` line that was previously there. At +this point, C++17 support should be in place.
+Try to compile the project to ensure it works as expected before following +further steps. + +Note that updating a *project* to C++17 doesn't necessarily mean that the IDE in +use will also start to recognize its syntax.
+If the plan is to use C++17 in the project too, check the specific instructions +for the IDE in use. + +## EnTT as a third party module + +Once this point is reached, the `Source` directory should look like this: + +``` +Source +| MyGame.Target.cs +| MyGameEditor.Target.cs +| ++---MyGame +| | MyGame.Build.cs +| | MyGame.h (PCH Header file) +| +\---ThirdParty + \---EnTT + | EnTT.Build.cs + | + \---entt (GitHub repository content inside) +``` + +To make this happen, create the folder `ThirdParty` under `Source` if it doesn't +exist already. Then, add an `EnTT` folder under `ThirdParty`.
+Within the latter, create a new file `EnTT.Build.cs` with the following content: + +```cs +using System.IO; +using UnrealBuildTool; + +public class EnTT: ModuleRules { + public EnTT(ReadOnlyTargetRules Target) : base(Target) { + Type = ModuleType.External; + PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "entt", "src", "entt")); + } +} +``` + +The last line indicates that the actual files will be found in the directory +`EnTT/entt/src/entt`.
+Download the repository for `EnTT` and place it next to `EnTT.Build.cs` or +update the path above accordingly. + +Finally, open the `.Build.cs` file and add `EnTT` as a dependency at +the end of the list: + +```cs +PublicDependencyModuleNames.AddRange(new[] { + "Core", "CoreUObject", "Engine", "InputCore", [...], "EnTT" +}); +``` + +Note that some IDEs might require a restart to start recognizing the new module +for code-highlighting features and such. + +## Include EnTT + +In any source file of the project, add `#include "entt.hpp"` or any other path +to the file from `EnTT` to use it.
+Try to create a registry as `entt::registry registry;` to make sure everything +compiles fine. diff --git a/modules/entt/natvis/entt/config.natvis b/modules/entt/natvis/entt/config.natvis new file mode 100644 index 0000000..6eb47e3 --- /dev/null +++ b/modules/entt/natvis/entt/config.natvis @@ -0,0 +1,3 @@ + + + diff --git a/modules/entt/natvis/entt/container.natvis b/modules/entt/natvis/entt/container.natvis new file mode 100644 index 0000000..3fbfa18 --- /dev/null +++ b/modules/entt/natvis/entt/container.natvis @@ -0,0 +1,33 @@ + + + + + + {{ 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 + + + + diff --git a/modules/entt/natvis/entt/core.natvis b/modules/entt/natvis/entt/core.natvis new file mode 100644 index 0000000..4a7d24e --- /dev/null +++ b/modules/entt/natvis/entt/core.natvis @@ -0,0 +1,33 @@ + + + + {{ type={ info->alias,na }, 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/modules/entt/natvis/entt/entity.natvis b/modules/entt/natvis/entt/entity.natvis new file mode 100644 index 0000000..db68847 --- /dev/null +++ b/modules/entt/natvis/entt/entity.natvis @@ -0,0 +1,145 @@ + + + + + + + + + {{ size={ entities.size() } }} + + entities,view(simple)nr + + { entities.size() } + + + + + + + + entities[pos] + + ++pos + + + + + + { to_entity(free_list) != entity_traits::entity_mask } + + + + + + entities[it] + it = to_entity(entities[it]) + + + + + + { pools_size() } + + + pools_size() + *pools.packed.first_base::value[$i].element.second + + + pools_size() + *pools.packed.first_base::value[$i].element.second,view(simple) + + + + groups.size() + + { vars_size() } + + + vars_size() + vars.data.packed.first_base::value[$i].element.second + + + + + + + {{ size={ packed.size() }, type={ info->alias,na } }} + + packed.capacity() + mode,en + + { sparse.size() * entity_traits::page_size } + + sparse,view(simple) + + + + + + + + page = pos / entity_traits::page_size + offset = pos & (entity_traits::page_size - 1) + + *((entity_traits::entity_type *)&sparse[page][offset]) & entity_traits::entity_mask + + ++pos + + + + + + { packed.size() } + + packed,view(simple) + + + + + + + packed[pos] + + ++pos + + + + + + + + {{ size={ base_type::packed.size() }, type={ base_type::info->alias,na } }} + + packed.first_base::value.capacity() * comp_traits::page_size + comp_traits::page_size + (base_type*)this,nand + (base_type*)this,view(simple)nand + + + + + + + + packed.first_base::value[pos / comp_traits::page_size][pos & (comp_traits::page_size - 1)] + + ++pos + + + + + + {{ size_hint={ view->packed.size() } }} + + pools,na + filter,na + + + + <null> + + + <tombstone> + + diff --git a/modules/entt/natvis/entt/locator.natvis b/modules/entt/natvis/entt/locator.natvis new file mode 100644 index 0000000..6eb47e3 --- /dev/null +++ b/modules/entt/natvis/entt/locator.natvis @@ -0,0 +1,3 @@ + + + diff --git a/modules/entt/natvis/entt/meta.natvis b/modules/entt/natvis/entt/meta.natvis new file mode 100644 index 0000000..8c0f3f3 --- /dev/null +++ b/modules/entt/natvis/entt/meta.natvis @@ -0,0 +1,197 @@ + + + + {{ type={ node->info->alias,na }, policy={ storage.mode,en } }} + {{}} + + *node + + + + {{ key_type={ key_type_node->info->alias,na }, mapped_type={ mapped_type_node->info->alias,na } }} + {{ key_type={ key_type_node->info->alias,na } }} + {{}} + + + {{ type={ type->info->alias,na } }} + {{}} + + + {{ type={ type->info->alias,na } }} + {{}} + + + {{ arity={ arity } }} + {{}} + + + + + + {{ type={ type->info->alias,na } }} + {{}} + + id + arity + has_property(entt::internal::meta_traits::is_const) + has_property(entt::internal::meta_traits::is_static) + + + + prop + next + *this + + + + + + + { *node } + {{}} + + node + + + + + + + {{ arity={ arity }, ret={ ret->info->alias,na } }} + {{}} + + id + has_property(entt::internal::meta_traits::is_const) + has_property(entt::internal::meta_traits::is_static) + + + + prop + next + *this + + + + + + + { *node } + {{}} + + node + + + + { any } + + + {{ key_type={ id.node->info->alias,na }, mapped_type={ value.node->info->alias,na } }} + {{ key_type={ id.node->info->alias,na } }} + {{}} + + id + value + + + + { *node } + {{}} + + node + + + + {{ value_type={ value_type_node->info->alias,na } }} + {{}} + + + {{ type={ type->info->alias,na } }} + {{}} + + arity + + + + + + + {{ type={ info->alias,na } }} + {{}} + + id + size_of + has_property(entt::internal::meta_traits::is_arithmetic) + has_property(entt::internal::meta_traits::is_array) + has_property(entt::internal::meta_traits::is_enum) + has_property(entt::internal::meta_traits::is_class) + has_property(entt::internal::meta_traits::is_pointer) + has_property(entt::internal::meta_traits::is_meta_pointer_like) + has_property(entt::internal::meta_traits::is_meta_sequence_container) + has_property(entt::internal::meta_traits::is_meta_associative_container) + default_constructor != nullptr + conversion_helper != nullptr + *templ + + + + ctor + next + *this + + + + + + + base + next + *this + + + + + + + conv + next + *this + + + + + + + data + next + *this + + + + + + + func + next + *this + + + + + + + prop + next + *this + + + + + + + { *node } + {{}} + + node + + + diff --git a/modules/entt/natvis/entt/platform.natvis b/modules/entt/natvis/entt/platform.natvis new file mode 100644 index 0000000..6eb47e3 --- /dev/null +++ b/modules/entt/natvis/entt/platform.natvis @@ -0,0 +1,3 @@ + + + diff --git a/modules/entt/natvis/entt/poly.natvis b/modules/entt/natvis/entt/poly.natvis new file mode 100644 index 0000000..8dfd72a --- /dev/null +++ b/modules/entt/natvis/entt/poly.natvis @@ -0,0 +1,6 @@ + + + + { storage } + + diff --git a/modules/entt/natvis/entt/process.natvis b/modules/entt/natvis/entt/process.natvis new file mode 100644 index 0000000..6eb47e3 --- /dev/null +++ b/modules/entt/natvis/entt/process.natvis @@ -0,0 +1,3 @@ + + + diff --git a/modules/entt/natvis/entt/resource.natvis b/modules/entt/natvis/entt/resource.natvis new file mode 100644 index 0000000..189bcaa --- /dev/null +++ b/modules/entt/natvis/entt/resource.natvis @@ -0,0 +1,15 @@ + + + + { value } + + value + + + + { pool.first_base::value } + + pool.first_base::value + + + diff --git a/modules/entt/natvis/entt/signal.natvis b/modules/entt/natvis/entt/signal.natvis new file mode 100644 index 0000000..30eb45a --- /dev/null +++ b/modules/entt/natvis/entt/signal.natvis @@ -0,0 +1,52 @@ + + + + {{ bound={ signal != nullptr } }} + + + {{ type={ "$T1" } }} + + fn == nullptr + instance + + + + {{ size={ pools.size() } }} + + + { pools.size() } + + + pools.size() + *pools.packed.first_base::value[$i].element.second + + + + + + + {{ size={ events.size() }, event={ "$T1" } }} + + signal + + + + { conn } + + + {{ size={ calls.size() }, type={ "$T1" } }} + + + calls.size() + calls[$i] + + + + + {{ type={ "$T1" } }} + + signal,na + offset + + + diff --git a/modules/entt/scripts/update_homebrew.sh b/modules/entt/scripts/update_homebrew.sh index 2c91c99..300a298 100644 --- a/modules/entt/scripts/update_homebrew.sh +++ b/modules/entt/scripts/update_homebrew.sh @@ -41,14 +41,14 @@ echo "Sedding..." # change the url in the formula file # the slashes in the URL must be escaped -ESCAPED_URL="$(sed -e 's/[\/&]/\\&/g' <<< "$URL")" +ESCAPED_URL="$(echo "$URL" | sed -e 's/[\/&]/\\&/g')" sed -i -e '/url/s/".*"/"'$ESCAPED_URL'"/' $FORMULA # change the hash in the formula file sed -i -e '/sha256/s/".*"/"'$HASH'"/' $FORMULA # delete temporary file created by sed -rm "$FORMULA-e" +rm -rf "$FORMULA-e" # update remote repo echo "Gitting..." diff --git a/modules/entt/scripts/update_packages.sh b/modules/entt/scripts/update_packages.sh deleted file mode 100644 index d8cc910..0000000 --- a/modules/entt/scripts/update_packages.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -scripts/update_homebrew.sh $1 diff --git a/modules/entt/single_include/entt/entt.hpp b/modules/entt/single_include/entt/entt.hpp index f183fc7..9b78225 100644 --- a/modules/entt/single_include/entt/entt.hpp +++ b/modules/entt/single_include/entt/entt.hpp @@ -1,14265 +1,64163 @@ -// #include "core/algorithm.hpp" -#ifndef ENTT_CORE_ALGORITHM_HPP -#define ENTT_CORE_ALGORITHM_HPP - - -#include -#include -#include - - -namespace entt { - +// #include "config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H -/** - * @brief Function object to wrap `std::sort` in a class type. - * - * Unfortunately, `std::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 `std::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 It Type of random access iterator. - * @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()(It first, It last, Compare compare = Compare{}, Args &&... args) const { - std::sort(std::forward(args)..., std::move(first), std::move(last), std::move(compare)); - } -}; +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H -/*! @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 It Type of random access iterator. - * @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()(It first, It last, Compare compare = Compare{}) const { - if(first != last) { - auto it = first + 1; +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) - while(it != last) { - auto pre = it++; - auto value = *pre; +#endif - while(pre-- != first && compare(value, *pre)) { - *(pre+1) = *pre; - } - *(pre+1) = value; - } - } - } -}; +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) -} +#endif -#endif // ENTT_CORE_ALGORITHM_HPP +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 -// #include "core/family.hpp" -#ifndef ENTT_CORE_FAMILY_HPP -#define ENTT_CORE_FAMILY_HPP +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "config/macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + +// #include "config/version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + +// #include "container/dense_map.hpp" +#ifndef ENTT_CONTAINER_DENSE_MAP_HPP +#define ENTT_CONTAINER_DENSE_MAP_HPP +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include // #include "../config/config.h" #ifndef ENTT_CONFIG_CONFIG_H #define ENTT_CONFIG_CONFIG_H +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H -#ifndef ENTT_NOEXCEPT -#define ENTT_NOEXCEPT noexcept -#endif // ENTT_NOEXCEPT - - -#ifndef ENTT_HS_SUFFIX -#define ENTT_HS_SUFFIX _hs -#endif // ENTT_HS_SUFFIX +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) -#ifndef ENTT_NO_ATOMIC -#include -template -using maybe_atomic_t = std::atomic; -#else // ENTT_NO_ATOMIC -template -using maybe_atomic_t = Type; -#endif // ENTT_NO_ATOMIC - +#endif -#ifndef ENTT_ID_TYPE -#include -#define ENTT_ID_TYPE std::uint32_t -#endif // ENTT_ID_TYPE +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 -#ifndef ENTT_PAGE_SIZE -#define ENTT_PAGE_SIZE 32768 -#endif // ENTT_PAGE_SIZE +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) +#endif -#ifndef ENTT_DISABLE_ASSERT -#include -#define ENTT_ASSERT(condition) assert(condition) -#else // ENTT_DISABLE_ASSERT -#define ENTT_ASSERT(...) ((void)0) -#endif // ENTT_DISABLE_ASSERT +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 -#endif // ENTT_CONFIG_CONFIG_H +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "../core/compressed_pair.hpp" +#ifndef ENTT_CORE_COMPRESSED_PAIR_HPP +#define ENTT_CORE_COMPRESSED_PAIR_HPP +#include +#include +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H -namespace entt { +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H -/** - * @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 { - inline static maybe_atomic_t identifier; +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) - template - inline static const auto inner = identifier++; +#endif -public: - /*! @brief Unsigned integer type. */ - using family_type = ENTT_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 in place of family_type here - inline static const family_type type = inner...>; -}; +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) -} +#endif -#endif // ENTT_CORE_FAMILY_HPP +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 -// #include "core/hashed_string.hpp" -#ifndef ENTT_CORE_HASHED_STRING_HPP -#define ENTT_CORE_HASHED_STRING_HPP +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP #include +#include +#include +#include // #include "../config/config.h" +// #include "fwd.hpp" +#ifndef ENTT_CORE_FWD_HPP +#define ENTT_CORE_FWD_HPP + +#include +#include +// #include "../config/config.h" namespace entt { +template)> +class basic_any; -/** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. - */ +/*! @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<>; -namespace internal { +} // namespace entt +#endif -template -struct fnv1a_traits; +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 TURN_OFF_DOXYGEN */ choice_t /*! @endcond */ +{}; +/*! @copybrief choice_t */ template<> -struct fnv1a_traits { - static constexpr std::uint32_t offset = 2166136261; - static constexpr std::uint32_t prime = 16777619; -}; +struct choice_t<0> {}; +/** + * @brief Variable template for the choice trick. + * @tparam N Number of choices available. + */ +template +inline constexpr choice_t choice{}; -template<> -struct fnv1a_traits { - static constexpr std::uint64_t offset = 14695981039346656037ull; - static constexpr std::uint64_t prime = 1099511628211ull; +/** + * @brief Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; }; +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; -} +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; /** - * Internal details not to be documented. - * @endcond TURN_OFF_DOXYGEN + * @brief Helper variable template. + * @tparam Type The type of which to return the size. */ +template +inline constexpr std::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 Zero overhead unique identifier. - * - * A hashed string is a compile-time tool that allows users to use - * human-readable identifers 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. + * @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. */ -class hashed_string { - using traits_type = internal::fnv1a_traits; +template +inline constexpr auto unpack_as_value = Value; - struct const_wrapper { - // non-explicit constructor on purpose - constexpr const_wrapper(const char *curr) ENTT_NOEXCEPT: str{curr} {} - const char *str; - }; +/** + * @brief Wraps a static constant. + * @tparam Value A static constant. + */ +template +using integral_constant = std::integral_constant; - // Fowler–Noll–Vo hash function v. 1a - the good - inline static constexpr ENTT_ID_TYPE helper(ENTT_ID_TYPE partial, const char *curr) ENTT_NOEXCEPT { - return curr[0] == 0 ? partial : helper((partial^curr[0])*traits_type::prime, curr+1); - } +/** + * @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; -public: - /*! @brief Unsigned integer type. */ - using hash_type = ENTT_ID_TYPE; +/** + * @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 Returns directly the numeric representation of a string. - * - * Forcing template resolution avoids implicit conversions. An - * human-readable identifier can be anything but a plain, old bunch of - * characters.
- * Example of use: - * @code{.cpp} - * const auto value = hashed_string::to_value("my.png"); - * @endcode - * - * @tparam N Number of characters of the identifier. - * @param str Human-readable identifer. - * @return The numeric representation of the string. - */ - template - inline static constexpr hash_type to_value(const char (&str)[N]) ENTT_NOEXCEPT { - return helper(traits_type::offset, str); - } +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_element; - /** - * @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. - */ - inline static hash_type to_value(const_wrapper wrapper) ENTT_NOEXCEPT { - return helper(traits_type::offset, wrapper.str); - } +/** + * @brief Provides compile-time indexed access to the types of a type list. + * @tparam Index Index of the type to return. + * @tparam Type 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 Returns directly the numeric representation of a string view. - * @param str Human-readable identifer. - * @param size Length of the string to hash. - * @return The numeric representation of the string. - */ - inline static hash_type to_value(const char *str, std::size_t size) ENTT_NOEXCEPT { - ENTT_ID_TYPE partial{traits_type::offset}; - while(size--) { partial = (partial^(str++)[0])*traits_type::prime; } - return partial; - } +/** + * @brief Provides compile-time indexed access to the types of a type list. + * @tparam Type 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 = Type; +}; - /*! @brief Constructs an empty hashed string. */ - constexpr hashed_string() ENTT_NOEXCEPT - : str{nullptr}, hash{} - {} +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; - /** - * @brief Constructs a hashed string from an array of const chars. - * - * Forcing template resolution avoids implicit conversions. An - * human-readable identifier can be anything but a plain, old bunch of - * characters.
- * Example of use: - * @code{.cpp} - * hashed_string hs{"my.png"}; - * @endcode - * - * @tparam N Number of characters of the identifier. - * @param curr Human-readable identifer. - */ - template - constexpr hashed_string(const char (&curr)[N]) ENTT_NOEXCEPT - : str{curr}, hash{helper(traits_type::offset, curr)} - {} +/** + * @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 +constexpr type_list operator+(type_list, type_list) { + return {}; +} - /** - * @brief Explicit constructor on purpose to avoid constructing a hashed - * string directly from a `const char *`. - * @param wrapper Helps achieving the purpose by relying on overloading. - */ - explicit constexpr hashed_string(const_wrapper wrapper) ENTT_NOEXCEPT - : str{wrapper.str}, hash{helper(traits_type::offset, wrapper.str)} - {} +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_cat; - /** - * @brief Returns the human-readable representation of a hashed string. - * @return The string used to initialize the instance. - */ - constexpr const char * data() const ENTT_NOEXCEPT { - return str; - } +/*! @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 Returns the numeric representation of a hashed string. - * @return The numeric representation of the instance. - */ - constexpr hash_type value() const ENTT_NOEXCEPT { - return hash; - } - - /** - * @brief Returns the human-readable representation of a hashed string. - * @return The string used to initialize the instance. - */ - constexpr operator const char *() const ENTT_NOEXCEPT { return str; } - - /*! @copydoc value */ - constexpr operator hash_type() const ENTT_NOEXCEPT { return hash; } - - /** - * @brief Compares two hashed strings. - * @param other Hashed string with which to compare. - * @return True if the two hashed strings are identical, false otherwise. - */ - constexpr bool operator==(const hashed_string &other) const ENTT_NOEXCEPT { - return hash == other.hash; - } - -private: - const char *str; - hash_type hash; +/** + * @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 = typename type_list_cat, List...>::type; }; - /** - * @brief Compares two hashed strings. - * @param lhs A valid hashed string. - * @param rhs A valid hashed string. - * @return True if the two hashed strings are identical, false otherwise. + * @brief Concatenates multiple type lists. + * @tparam Type Types provided by the type list. */ -constexpr bool operator!=(const hashed_string &lhs, const hashed_string &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); -} - +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 = typename type_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; /** - * @brief User defined literal for hashed strings. - * @param str The literal without its suffix. - * @return A properly initialized hashed string. + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. */ -constexpr entt::hashed_string operator"" ENTT_HS_SUFFIX(const char *str, std::size_t) ENTT_NOEXCEPT { - return entt::hashed_string{str}; -} - +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; +}; -#endif // ENTT_CORE_HASHED_STRING_HPP +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; -// #include "core/ident.hpp" -#ifndef ENTT_CORE_IDENT_HPP -#define ENTT_CORE_IDENT_HPP +/** + * @brief Helper type. + * @tparam Type A type list. + */ +template +using type_list_unique_t = typename type_list_unique::type; +/** + * @brief Provides the member constant `value` 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; -#include -#include -#include -// #include "../config/config.h" +/** + * @copybrief type_list_contains + * @tparam Type Types provided by the type list. + * @tparam Other Type to look for. + */ +template +struct type_list_contains, Other>: std::disjunction...> {}; +/** + * @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; -namespace entt { +/** + * @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 = typename type_list_diff::type; /** - * @brief Types identifiers. - * - * Variable template used to generate identifiers at compile-time for the given - * types. Use the `get` member function to know what's the identifier associated - * to the specific type. - * - * @note - * Identifiers are constant expression and can be used in any context where such - * an expression is required. As an example: - * @code{.cpp} - * using id = entt::identifier; - * - * switch(a_type_identifier) { - * case id::type: - * // ... - * break; - * case id::type: - * // ... - * break; - * default: - * // ... - * } - * @endcode - * - * @tparam Types List of types for which to generate identifiers. + * @brief A class to use to push around lists of constant values, nothing more. + * @tparam Value Values provided by the value list. */ -template -class identifier { - using tuple_type = std::tuple...>; +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); +}; - template - static constexpr ENTT_ID_TYPE get(std::index_sequence) ENTT_NOEXCEPT { - static_assert(std::disjunction_v...>); - return (0 + ... + (std::is_same_v> ? ENTT_ID_TYPE(Indexes) : ENTT_ID_TYPE{})); - } +/*! @brief Primary template isn't defined on purpose. */ +template +struct value_list_element; -public: - /*! @brief Unsigned integer type. */ - using identifier_type = ENTT_ID_TYPE; +/** + * @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 Statically generated unique identifier for the given type. */ - template - static constexpr identifier_type type = get>(std::make_index_sequence{}); +/** + * @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 value. */ + static constexpr auto value = Value; }; +/** + * @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 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 +constexpr value_list operator+(value_list, value_list) { + return {}; } +/*! @brief Primary template isn't defined on purpose. */ +template +struct value_list_cat; -#endif // ENTT_CORE_IDENT_HPP +/*! @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<>; +}; -// #include "core/monostate.hpp" -#ifndef ENTT_CORE_MONOSTATE_HPP -#define ENTT_CORE_MONOSTATE_HPP +/** + * @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 = typename 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; +}; -#include -// #include "../config/config.h" +/** + * @brief Helper type. + * @tparam List Value lists to concatenate. + */ +template +using value_list_cat_t = typename value_list_cat::type; -// #include "hashed_string.hpp" +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::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>: std::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>: std::is_invocable {}; -namespace entt { +/** + * @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 std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::false_type {}; /** - * @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. + * @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 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. - */ - template - void operator=(Type val) const ENTT_NOEXCEPT { - value = val; - } +template +struct is_applicable_r>: std::is_invocable_r {}; - /** - * @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 ENTT_NOEXCEPT { - return value; - } +/** + * @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; -private: - template - inline static maybe_atomic_t value{}; -}; +/** + * @brief Provides the member constant `value` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; +/*! @copydoc is_complete */ +template +struct is_complete>: std::true_type {}; /** * @brief Helper variable template. - * @tparam Value Value used to differentiate between different variables. + * @tparam Type The type to test. */ -template -inline monostate monostate_v = {}; - - -} +template +inline constexpr bool is_complete_v = is_complete::value; +/** + * @brief Provides the member constant `value` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; -#endif // ENTT_CORE_MONOSTATE_HPP +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ -// #include "core/type_traits.hpp" -#ifndef ENTT_CORE_TYPE_TRAITS_HPP -#define ENTT_CORE_TYPE_TRAITS_HPP +namespace internal { +template +struct has_iterator_category: std::false_type {}; -#include -// #include "../core/hashed_string.hpp" -#ifndef ENTT_CORE_HASHED_STRING_HPP -#define ENTT_CORE_HASHED_STRING_HPP +template +struct has_iterator_category::iterator_category>>: std::true_type {}; +} // namespace internal -#include -// #include "../config/config.h" -#ifndef ENTT_CONFIG_CONFIG_H -#define ENTT_CONFIG_CONFIG_H +/** + * Internal details not to be documented. + * @endcond + */ +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : internal::has_iterator_category {}; -#ifndef ENTT_NOEXCEPT -#define ENTT_NOEXCEPT noexcept -#endif // ENTT_NOEXCEPT +/** + * @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` 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 + : std::conjunction, std::negation>> {}; -#ifndef ENTT_HS_SUFFIX -#define ENTT_HS_SUFFIX _hs -#endif // ENTT_HS_SUFFIX +/** + * @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` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; -#ifndef ENTT_NO_ATOMIC -#include -template -using maybe_atomic_t = std::atomic; -#else // ENTT_NO_ATOMIC +/*! @copydoc is_transparent */ template -using maybe_atomic_t = Type; -#endif // ENTT_NO_ATOMIC +struct is_transparent>: std::true_type {}; +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; -#ifndef ENTT_ID_TYPE -#include -#define ENTT_ID_TYPE std::uint32_t -#endif // ENTT_ID_TYPE - +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; -#ifndef ENTT_PAGE_SIZE -#define ENTT_PAGE_SIZE 32768 -#endif // ENTT_PAGE_SIZE +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ +namespace internal { -#ifndef ENTT_DISABLE_ASSERT -#include -#define ENTT_ASSERT(condition) assert(condition) -#else // ENTT_DISABLE_ASSERT -#define ENTT_ASSERT(...) ((void)0) -#endif // ENTT_DISABLE_ASSERT +template +struct has_tuple_size_value: std::false_type {}; +template +struct has_tuple_size_value::value)>>: std::true_type {}; -#endif // ENTT_CONFIG_CONFIG_H +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; + } +} -namespace entt { +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); + } +} +} // namespace internal /** - * @cond TURN_OFF_DOXYGEN * Internal details not to be documented. + * @endcond */ +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; -namespace internal { +/** + * @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 = std::remove_const_t; +}; -template -struct fnv1a_traits; +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; +/** + * @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 = typename constness_as::type; -template<> -struct fnv1a_traits { - static constexpr std::uint32_t offset = 2166136261; - static constexpr std::uint32_t prime = 16777619; -}; +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); + template + static Class *clazz(Ret (Class::*)(Args...)); -template<> -struct fnv1a_traits { - static constexpr std::uint64_t offset = 14695981039346656037ull; - static constexpr std::uint64_t prime = 1099511628211ull; + 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 = std::remove_pointer_t()))>; }; +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; + +} // namespace entt -} +#endif +namespace entt { + /** + * @cond TURN_OFF_DOXYGEN * Internal details not to be documented. - * @endcond TURN_OFF_DOXYGEN */ +namespace internal { + +template +struct compressed_pair_element { + using reference = Type &; + using const_reference = const Type &; -/** - * @brief Zero overhead unique identifier. - * - * A hashed string is a compile-time tool that allows users to use - * human-readable identifers 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. - */ -class hashed_string { - using traits_type = internal::fnv1a_traits; + template>> + compressed_pair_element() + : value{} {} - struct const_wrapper { - // non-explicit constructor on purpose - constexpr const_wrapper(const char *curr) ENTT_NOEXCEPT: str{curr} {} - const char *str; - }; + template>, compressed_pair_element>>> + compressed_pair_element(Args &&args) + : value{std::forward(args)} {} - // Fowler–Noll–Vo hash function v. 1a - the good - inline static constexpr ENTT_ID_TYPE helper(ENTT_ID_TYPE partial, const char *curr) ENTT_NOEXCEPT { - return curr[0] == 0 ? partial : helper((partial^curr[0])*traits_type::prime, curr+1); + template + compressed_pair_element(std::tuple args, std::index_sequence) + : value{std::forward(std::get(args))...} {} + + [[nodiscard]] reference get() ENTT_NOEXCEPT { + return value; + } + + [[nodiscard]] const_reference get() const ENTT_NOEXCEPT { + return value; } +private: + Type value; +}; + +template +struct compressed_pair_element>>: Type { + using reference = Type &; + using const_reference = const Type &; + using base_type = Type; + + template>> + compressed_pair_element() + : base_type{} {} + + template>, compressed_pair_element>>> + compressed_pair_element(Args &&args) + : base_type{std::forward(args)} {} + + template + compressed_pair_element(std::tuple args, std::index_sequence) + : base_type{std::forward(std::get(args))...} {} + + [[nodiscard]] reference get() ENTT_NOEXCEPT { + return *this; + } + + [[nodiscard]] const_reference get() const ENTT_NOEXCEPT { + return *this; + } +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @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 Unsigned integer type. */ - using hash_type = ENTT_ID_TYPE; + /*! @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 Returns directly the numeric representation of a string. + * @brief Default constructor, conditionally enabled. * - * Forcing template resolution avoids implicit conversions. An - * human-readable identifier can be anything but a plain, old bunch of - * characters.
- * Example of use: - * @code{.cpp} - * const auto value = hashed_string::to_value("my.png"); - * @endcode + * This constructor is only available when the types that the pair stores + * are both at least default constructible. * - * @tparam N Number of characters of the identifier. - * @param str Human-readable identifer. - * @return The numeric representation of the string. + * @tparam Dummy Dummy template parameter used for internal purposes. */ - template - inline static constexpr hash_type to_value(const char (&str)[N]) ENTT_NOEXCEPT { - return helper(traits_type::offset, str); - } + template && std::is_default_constructible_v>> + constexpr compressed_pair() + : first_base{}, + second_base{} {} /** - * @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. + * @brief Copy constructor. + * @param other The instance to copy from. */ - inline static hash_type to_value(const_wrapper wrapper) ENTT_NOEXCEPT { - return helper(traits_type::offset, wrapper.str); - } + constexpr compressed_pair(const compressed_pair &other) = default; /** - * @brief Returns directly the numeric representation of a string view. - * @param str Human-readable identifer. - * @param size Length of the string to hash. - * @return The numeric representation of the string. + * @brief Move constructor. + * @param other The instance to move from. */ - inline static hash_type to_value(const char *str, std::size_t size) ENTT_NOEXCEPT { - ENTT_ID_TYPE partial{traits_type::offset}; - while(size--) { partial = (partial^(str++)[0])*traits_type::prime; } - return partial; - } + constexpr compressed_pair(compressed_pair &&other) = default; - /*! @brief Constructs an empty hashed string. */ - constexpr hashed_string() ENTT_NOEXCEPT - : str{nullptr}, hash{} - {} + /** + * @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) + : first_base{std::forward(arg)}, + second_base{std::forward(other)} {} /** - * @brief Constructs a hashed string from an array of const chars. - * - * Forcing template resolution avoids implicit conversions. An - * human-readable identifier can be anything but a plain, old bunch of - * characters.
- * Example of use: - * @code{.cpp} - * hashed_string hs{"my.png"}; - * @endcode - * - * @tparam N Number of characters of the identifier. - * @param curr Human-readable identifer. + * @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 hashed_string(const char (&curr)[N]) ENTT_NOEXCEPT - : str{curr}, hash{helper(traits_type::offset, curr)} - {} + template + constexpr compressed_pair(std::piecewise_construct_t, std::tuple args, std::tuple other) + : first_base{std::move(args), std::index_sequence_for{}}, + second_base{std::move(other), std::index_sequence_for{}} {} /** - * @brief Explicit constructor on purpose to avoid constructing a hashed - * string directly from a `const char *`. - * @param wrapper Helps achieving the purpose by relying on overloading. + * @brief Copy assignment operator. + * @param other The instance to copy from. + * @return This compressed pair object. */ - explicit constexpr hashed_string(const_wrapper wrapper) ENTT_NOEXCEPT - : str{wrapper.str}, hash{helper(traits_type::offset, wrapper.str)} - {} + constexpr compressed_pair &operator=(const compressed_pair &other) = default; /** - * @brief Returns the human-readable representation of a hashed string. - * @return The string used to initialize the instance. + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This compressed pair object. */ - constexpr const char * data() const ENTT_NOEXCEPT { - return str; - } + constexpr compressed_pair &operator=(compressed_pair &&other) = default; /** - * @brief Returns the numeric representation of a hashed string. - * @return The numeric representation of the instance. + * @brief Returns the first element that a pair stores. + * @return The first element that a pair stores. */ - constexpr hash_type value() const ENTT_NOEXCEPT { - return hash; + [[nodiscard]] first_type &first() ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /*! @copydoc first */ + [[nodiscard]] const first_type &first() const ENTT_NOEXCEPT { + return static_cast(*this).get(); } /** - * @brief Returns the human-readable representation of a hashed string. - * @return The string used to initialize the instance. + * @brief Returns the second element that a pair stores. + * @return The second element that a pair stores. */ - constexpr operator const char *() const ENTT_NOEXCEPT { return str; } + [[nodiscard]] second_type &second() ENTT_NOEXCEPT { + return static_cast(*this).get(); + } - /*! @copydoc value */ - constexpr operator hash_type() const ENTT_NOEXCEPT { return hash; } + /*! @copydoc second */ + [[nodiscard]] const second_type &second() const ENTT_NOEXCEPT { + return static_cast(*this).get(); + } /** - * @brief Compares two hashed strings. - * @param other Hashed string with which to compare. - * @return True if the two hashed strings are identical, false otherwise. + * @brief Swaps two compressed pair objects. + * @param other The compressed pair to swap with. */ - constexpr bool operator==(const hashed_string &other) const ENTT_NOEXCEPT { - return hash == other.hash; + void swap(compressed_pair &other) { + using std::swap; + swap(first(), other.first()); + swap(second(), other.second()); } -private: - const char *str; - hash_type hash; + /** + * @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 + decltype(auto) get() ENTT_NOEXCEPT { + if constexpr(Index == 0u) { + return first(); + } else { + static_assert(Index == 1u, "Index out of bounds"); + return second(); + } + } + + /*! @copydoc get */ + template + decltype(auto) get() const ENTT_NOEXCEPT { + if constexpr(Index == 0u) { + return first(); + } else { + static_assert(Index == 1u, "Index out of bounds"); + 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, std::decay_t>; /** - * @brief Compares two hashed strings. - * @param lhs A valid hashed string. - * @param rhs A valid hashed string. - * @return True if the two hashed strings are identical, false otherwise. + * @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. */ -constexpr bool operator!=(const hashed_string &lhs, const hashed_string &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); +template +inline void swap(compressed_pair &lhs, compressed_pair &rhs) { + lhs.swap(rhs); } +} // namespace entt -} +// disable structured binding support for clang 6, it messes when specializing tuple_size +#if !defined __clang_major__ || __clang_major__ > 6 +namespace std { +/** + * @brief `std::tuple_size` specialization for `compressed_pair`s. + * @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 +struct tuple_size>: integral_constant {}; /** - * @brief User defined literal for hashed strings. - * @param str The literal without its suffix. - * @return A properly initialized hashed string. + * @brief `std::tuple_element` specialization for `compressed_pair`s. + * @tparam Index The index of the type to return. + * @tparam First The type of the first element that the pair stores. + * @tparam Second The type of the second element that the pair stores. */ -constexpr entt::hashed_string operator"" ENTT_HS_SUFFIX(const char *str, std::size_t) ENTT_NOEXCEPT { - return entt::hashed_string{str}; -} +template +struct tuple_element>: conditional { + static_assert(Index < 2u, "Index out of bounds"); +}; +} // namespace std +#endif -#endif // ENTT_CORE_HASHED_STRING_HPP +#endif +// #include "../core/iterator.hpp" +#ifndef ENTT_CORE_ITERATOR_HPP +#define ENTT_CORE_ITERATOR_HPP + +#include +#include +#include +// #include "../config/config.h" namespace entt { - /** - * @brief A class to use to push around lists of types, nothing more. - * @tparam Type Types provided by the given type list. + * @brief Helper type to use as pointer with input iterators. + * @tparam Type of wrapped value. */ -template -struct type_list { - /*! @brief Unsigned integer type. */ - static constexpr auto size = sizeof...(Type); -}; +template +struct input_iterator_pointer final { + /*! @brief Pointer type. */ + using pointer = Type *; + /*! @brief Default copy constructor, deleted on purpose. */ + input_iterator_pointer(const input_iterator_pointer &) = delete; -/*! @brief Primary template isn't defined on purpose. */ -template -struct type_list_cat; + /*! @brief Default move constructor. */ + input_iterator_pointer(input_iterator_pointer &&) = default; + /** + * @brief Constructs a proxy object by move. + * @param val Value to use to initialize the proxy object. + */ + input_iterator_pointer(Type &&val) + : value{std::move(val)} {} -/*! @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 Default copy assignment operator, deleted on purpose. + * @return This proxy object. + */ + input_iterator_pointer &operator=(const input_iterator_pointer &) = delete; + /** + * @brief Default move assignment operator. + * @return This proxy object. + */ + input_iterator_pointer &operator=(input_iterator_pointer &&) = default; -/** - * @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 = typename type_list_cat, List...>::type; -}; + /** + * @brief Access operator for accessing wrapped values. + * @return A pointer to the wrapped value. + */ + [[nodiscard]] pointer operator->() ENTT_NOEXCEPT { + return std::addressof(value); + } +private: + Type value; +}; /** - * @brief Concatenates multiple type lists. - * @tparam Type Types provided by the type list. + * @brief Utility class to create an iterable object from a pair of iterators. + * @tparam It Type of iterator. + * @tparam Sentinel Type of sentinel. */ -template -struct type_list_cat> { - /*! @brief A type list composed by the types of all the type lists. */ - using type = type_list; -}; +template +struct iterable_adaptor final { + /*! @brief Value type. */ + using value_type = typename std::iterator_traits::value_type; + /*! @brief Iterator type. */ + using iterator = It; + /*! @brief Sentinel type. */ + using sentinel = Sentinel; + /*! @brief Default constructor. */ + iterable_adaptor() = default; -/** - * @brief Helper type. - * @tparam List Type lists to concatenate. - */ -template -using type_list_cat_t = typename type_list_cat::type; + /** + * @brief Creates an iterable object from a pair of iterators. + * @param from Begin iterator. + * @param to End iterator. + */ + iterable_adaptor(iterator from, sentinel to) + : first{from}, + last{to} {} + /** + * @brief Returns an iterator to the beginning. + * @return An iterator to the first element of the range. + */ + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return first; + } -/*! @brief Primary template isn't defined on purpose. */ -template -struct type_list_unique; + /** + * @brief Returns an iterator to the end. + * @return An iterator to the element following the last element of the + * range. + */ + [[nodiscard]] sentinel end() const ENTT_NOEXCEPT { + return last; + } + /*! @copydoc begin */ + [[nodiscard]] iterator cbegin() const ENTT_NOEXCEPT { + return begin(); + } -/** - * @brief Removes duplicates types from a type list. - * @tparam Type One of the types provided by the given type list. - * @tparam Other The other types provided by the given type list. - */ -template -struct type_list_unique> { - /*! @brief A type list without duplicate types. */ - using type = std::conditional_t< - std::disjunction_v...>, - typename type_list_unique>::type, - type_list_cat_t, typename type_list_unique>::type> - >; -}; - + /*! @copydoc end */ + [[nodiscard]] sentinel cend() const ENTT_NOEXCEPT { + return end(); + } -/*! @brief Removes duplicates types from a type list. */ -template<> -struct type_list_unique> { - /*! @brief A type list without duplicate types. */ - using type = type_list<>; +private: + It first; + Sentinel last; }; +} // namespace entt -/** - * @brief Helper type. - * @tparam Type A type list. - */ -template -using type_list_unique_t = typename type_list_unique::type; +#endif +// #include "../core/memory.hpp" +#ifndef ENTT_CORE_MEMORY_HPP +#define ENTT_CORE_MEMORY_HPP + +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" -/*! @brief Traits class used mainly to push things across boundaries. */ -template -struct named_type_traits; +namespace entt { /** - * @brief Specialization used to get rid of constness. - * @tparam Type Named type. + * @brief Unwraps fancy pointers, does nothing otherwise (waiting for C++20). + * @tparam Type Pointer type. + * @param ptr Fancy or raw pointer. + * @return A raw pointer that represents the address of the original pointer. */ template -struct named_type_traits - : named_type_traits -{}; - +[[nodiscard]] constexpr auto to_address(Type &&ptr) ENTT_NOEXCEPT { + if constexpr(std::is_pointer_v>>) { + return ptr; + } else { + return to_address(std::forward(ptr).operator->()); + } +} /** - * @brief Helper type. - * @tparam Type Potentially named type. + * @brief Utility function to design allocation-aware containers. + * @tparam Allocator Type of allocator. + * @param lhs A valid allocator. + * @param rhs Another valid allocator. */ -template -using named_type_traits_t = typename named_type_traits::type; - +template +constexpr void propagate_on_container_copy_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) ENTT_NOEXCEPT { + if constexpr(std::allocator_traits::propagate_on_container_copy_assignment::value) { + lhs = rhs; + } +} /** - * @brief Provides the member constant `value` to true if a given type has a - * name. In all other cases, `value` is false. + * @brief Utility function to design allocation-aware containers. + * @tparam Allocator Type of allocator. + * @param lhs A valid allocator. + * @param rhs Another valid allocator. */ -template> -struct is_named_type: std::false_type {}; - +template +constexpr void propagate_on_container_move_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) ENTT_NOEXCEPT { + if constexpr(std::allocator_traits::propagate_on_container_move_assignment::value) { + lhs = std::move(rhs); + } +} /** - * @brief Provides the member constant `value` to true if a given type has a - * name. In all other cases, `value` is false. - * @tparam Type Potentially named type. + * @brief Utility function to design allocation-aware containers. + * @tparam Allocator Type of allocator. + * @param lhs A valid allocator. + * @param rhs Another valid allocator. */ -template -struct is_named_type>>>: std::true_type {}; +template +constexpr void propagate_on_container_swap([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) ENTT_NOEXCEPT { + ENTT_ASSERT(std::allocator_traits::propagate_on_container_swap::value || lhs == rhs, "Cannot swap the containers"); + if constexpr(std::allocator_traits::propagate_on_container_swap::value) { + using std::swap; + swap(lhs, rhs); + } +} /** - * @brief Helper variable template. - * - * True if a given type has a name, false otherwise. - * - * @tparam Type Potentially named type. + * @brief Checks whether a value is a power of two or not. + * @param value A value that may or may not be a power of two. + * @return True if the value is a power of two, false otherwise. */ -template -constexpr auto is_named_type_v = is_named_type::value; - - +[[nodiscard]] inline constexpr bool is_power_of_two(const std::size_t value) ENTT_NOEXCEPT { + return value && ((value & (value - 1)) == 0); } - /** - * @brief Utility macro to deal with an issue of MSVC. - * - * See _msvc-doesnt-expand-va-args-correctly_ on SO for all the details. - * - * @param args Argument to expand. + * @brief Computes the smallest power of two greater than or equal to a value. + * @param value The value to use. + * @return The smallest power of two greater than or equal to the given value. */ -#define ENTT_EXPAND(args) args +[[nodiscard]] inline constexpr std::size_t next_power_of_two(const std::size_t value) ENTT_NOEXCEPT { + ENTT_ASSERT(value < (std::size_t{1u} << (std::numeric_limits::digits - 1)), "Numeric limits exceeded"); + std::size_t curr = value - (value != 0u); + + for(int next = 1; next < std::numeric_limits::digits; next = next * 2) { + curr |= curr >> next; + } + return ++curr; +} /** - * @brief Makes an already existing type a named type. - * @param type Type to assign a name to. + * @brief Fast module utility function (powers of two only). + * @param value A value for which to calculate the modulus. + * @param mod _Modulus_, it must be a power of two. + * @return The common remainder. */ -#define ENTT_NAMED_TYPE(type)\ - template<>\ - struct entt::named_type_traits\ - : std::integral_constant\ - {\ - static_assert(std::is_same_v, type>);\ - }; - +[[nodiscard]] inline constexpr std::size_t fast_mod(const std::size_t value, const std::size_t mod) ENTT_NOEXCEPT { + ENTT_ASSERT(is_power_of_two(mod), "Value must be a power of two"); + return value & (mod - 1u); +} /** - * @brief Defines a named type (to use for structs). - * @param clazz Name of the type to define. - * @param body Body of the type to define. + * @brief Deleter for allocator-aware unique pointers (waiting for C++20). + * @tparam Args Types of arguments to use to construct the object. */ -#define ENTT_NAMED_STRUCT_ONLY(clazz, body)\ - struct clazz body;\ - ENTT_NAMED_TYPE(clazz) +template +struct allocation_deleter: private Allocator { + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Pointer type. */ + using pointer = typename std::allocator_traits::pointer; + + /** + * @brief Inherited constructors. + * @param alloc The allocator to use. + */ + allocation_deleter(const allocator_type &alloc) + : Allocator{alloc} {} + /** + * @brief Destroys the pointed object and deallocates its memory. + * @param ptr A valid pointer to an object of the given type. + */ + void operator()(pointer ptr) { + using alloc_traits = typename std::allocator_traits; + alloc_traits::destroy(*this, to_address(ptr)); + alloc_traits::deallocate(*this, ptr, 1u); + } +}; /** - * @brief Defines a named type (to use for structs). - * @param ns Namespace where to define the named type. - * @param clazz Name of the type to define. - * @param body Body of the type to define. + * @brief Allows `std::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. */ -#define ENTT_NAMED_STRUCT_WITH_NAMESPACE(ns, clazz, body)\ - namespace ns { struct clazz body; }\ - ENTT_NAMED_TYPE(ns::clazz) +template +auto allocate_unique(Allocator &allocator, Args &&...args) { + static_assert(!std::is_array_v, "Array types are not supported"); + using alloc_traits = typename std::allocator_traits::template rebind_traits; + using allocator_type = typename alloc_traits::allocator_type; -/*! @brief Utility function to simulate macro overloading. */ -#define ENTT_NAMED_STRUCT_OVERLOAD(_1, _2, _3, FUNC, ...) FUNC -/*! @brief Defines a named type (to use for structs). */ -#define ENTT_NAMED_STRUCT(...) ENTT_EXPAND(ENTT_NAMED_STRUCT_OVERLOAD(__VA_ARGS__, ENTT_NAMED_STRUCT_WITH_NAMESPACE, ENTT_NAMED_STRUCT_ONLY,)(__VA_ARGS__)) + allocator_type alloc{allocator}; + auto ptr = alloc_traits::allocate(alloc, 1u); + ENTT_TRY { + alloc_traits::construct(alloc, to_address(ptr), std::forward(args)...); + } + ENTT_CATCH { + alloc_traits::deallocate(alloc, ptr, 1u); + ENTT_THROW; + } + + return std::unique_ptr>{ptr, alloc}; +} /** - * @brief Defines a named type (to use for classes). - * @param clazz Name of the type to define. - * @param body Body of the type to define. + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. */ -#define ENTT_NAMED_CLASS_ONLY(clazz, body)\ - class clazz body;\ - ENTT_NAMED_TYPE(clazz) +namespace internal { -/** - * @brief Defines a named type (to use for classes). - * @param ns Namespace where to define the named type. - * @param clazz Name of the type to define. - * @param body Body of the type to define. - */ -#define ENTT_NAMED_CLASS_WITH_NAMESPACE(ns, clazz, body)\ - namespace ns { class clazz body; }\ - ENTT_NAMED_TYPE(ns::clazz) +template +struct uses_allocator_construction { + template + static constexpr auto args([[maybe_unused]] const Allocator &allocator, Params &&...params) ENTT_NOEXCEPT { + if constexpr(!std::uses_allocator_v && std::is_constructible_v) { + return std::forward_as_tuple(std::forward(params)...); + } else { + static_assert(std::uses_allocator_v, "Ill-formed request"); + if constexpr(std::is_constructible_v) { + return std::tuple(std::allocator_arg, allocator, std::forward(params)...); + } else { + static_assert(std::is_constructible_v, "Ill-formed request"); + return std::forward_as_tuple(std::forward(params)..., allocator); + } + } + } +}; -/*! @brief Utility function to simulate macro overloading. */ -#define ENTT_NAMED_CLASS_MACRO(_1, _2, _3, FUNC, ...) FUNC -/*! @brief Defines a named type (to use for classes). */ -#define ENTT_NAMED_CLASS(...) ENTT_EXPAND(ENTT_NAMED_CLASS_MACRO(__VA_ARGS__, ENTT_NAMED_CLASS_WITH_NAMESPACE, ENTT_NAMED_CLASS_ONLY,)(__VA_ARGS__)) +template +struct uses_allocator_construction> { + using type = std::pair; + template + static constexpr auto args(const Allocator &allocator, std::piecewise_construct_t, First &&first, Second &&second) ENTT_NOEXCEPT { + return std::make_tuple( + std::piecewise_construct, + std::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, std::forward(curr)...); }, std::forward(first)), + std::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, std::forward(curr)...); }, std::forward(second))); + } -#endif // ENTT_CORE_TYPE_TRAITS_HPP + template + static constexpr auto args(const Allocator &allocator) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::tuple<>{}, std::tuple<>{}); + } -// #include "core/utility.hpp" -#ifndef ENTT_CORE_UTILITY_HPP -#define ENTT_CORE_UTILITY_HPP + template + static constexpr auto args(const Allocator &allocator, First &&first, Second &&second) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::forward(first)), std::forward_as_tuple(std::forward(second))); + } + template + static constexpr auto args(const Allocator &allocator, const std::pair &value) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(value.first), std::forward_as_tuple(value.second)); + } -namespace entt { + template + static constexpr auto args(const Allocator &allocator, std::pair &&value) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::move(value.first)), std::forward_as_tuple(std::move(value.second))); + } +}; +} // namespace internal /** - * @brief Constant utility to disambiguate overloaded member functions. - * @tparam Type Function type of the desired overload. - * @tparam Class Type of class to which the member functions belong. - * @param member A valid pointer to a member function. - * @return Pointer to the member function. + * Internal details not to be documented. + * @endcond */ -template -constexpr auto overload(Type Class:: *member) { return member; } - /** - * @brief Constant utility to disambiguate overloaded functions. - * @tparam Type Function type of the desired overload. - * @param func A valid pointer to a function. - * @return Pointer to the function. + * @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 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 The arguments needed to create an object of the given type. */ -template -constexpr auto overload(Type *func) { return func; } - - +template +constexpr auto uses_allocator_construction_args(const Allocator &allocator, Args &&...args) ENTT_NOEXCEPT { + return internal::uses_allocator_construction::args(allocator, std::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 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 newly created object of the given type. + */ +template +constexpr Type make_obj_using_allocator(const Allocator &allocator, Args &&...args) { + return std::make_from_tuple(internal::uses_allocator_construction::args(allocator, std::forward(args)...)); +} -#endif // ENTT_CORE_UTILITY_HPP - -// #include "entity/actor.hpp" -#ifndef ENTT_ENTITY_ACTOR_HPP -#define ENTT_ENTITY_ACTOR_HPP - - -#include -#include -#include -// #include "../config/config.h" -#ifndef ENTT_CONFIG_CONFIG_H -#define ENTT_CONFIG_CONFIG_H - - -#ifndef ENTT_NOEXCEPT -#define ENTT_NOEXCEPT noexcept -#endif // ENTT_NOEXCEPT - - -#ifndef ENTT_HS_SUFFIX -#define ENTT_HS_SUFFIX _hs -#endif // ENTT_HS_SUFFIX - - -#ifndef ENTT_NO_ATOMIC -#include -template -using maybe_atomic_t = std::atomic; -#else // ENTT_NO_ATOMIC -template -using maybe_atomic_t = Type; -#endif // ENTT_NO_ATOMIC - - -#ifndef ENTT_ID_TYPE -#include -#define ENTT_ID_TYPE std::uint32_t -#endif // ENTT_ID_TYPE - - -#ifndef ENTT_PAGE_SIZE -#define ENTT_PAGE_SIZE 32768 -#endif // ENTT_PAGE_SIZE - - -#ifndef ENTT_DISABLE_ASSERT -#include -#define ENTT_ASSERT(condition) assert(condition) -#else // ENTT_DISABLE_ASSERT -#define ENTT_ASSERT(...) ((void)0) -#endif // ENTT_DISABLE_ASSERT - +/** + * @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 Allocator Type of allocator used to manage memory and elements. + * @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 Allocator &allocator, Args &&...args) { + return std::apply([&](auto &&...curr) { return new(value) Type(std::forward(curr)...); }, internal::uses_allocator_construction::args(allocator, std::forward(args)...)); +} -#endif // ENTT_CONFIG_CONFIG_H +} // namespace entt -// #include "registry.hpp" -#ifndef ENTT_ENTITY_REGISTRY_HPP -#define ENTT_ENTITY_REGISTRY_HPP +#endif +// #include "../core/type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP -#include -#include -#include -#include #include -#include #include -#include #include +#include // #include "../config/config.h" -// #include "../core/family.hpp" -#ifndef ENTT_CORE_FAMILY_HPP -#define ENTT_CORE_FAMILY_HPP - - -#include -// #include "../config/config.h" -#ifndef ENTT_CONFIG_CONFIG_H -#define ENTT_CONFIG_CONFIG_H +// #include "fwd.hpp" -#ifndef ENTT_NOEXCEPT -#define ENTT_NOEXCEPT noexcept -#endif // ENTT_NOEXCEPT +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 TURN_OFF_DOXYGEN */ choice_t /*! @endcond */ +{}; -#ifndef ENTT_HS_SUFFIX -#define ENTT_HS_SUFFIX _hs -#endif // ENTT_HS_SUFFIX +/*! @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{}; -#ifndef ENTT_NO_ATOMIC -#include -template -using maybe_atomic_t = std::atomic; -#else // ENTT_NO_ATOMIC +/** + * @brief Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ template -using maybe_atomic_t = Type; -#endif // ENTT_NO_ATOMIC - - -#ifndef ENTT_ID_TYPE -#include -#define ENTT_ID_TYPE std::uint32_t -#endif // ENTT_ID_TYPE - - -#ifndef ENTT_PAGE_SIZE -#define ENTT_PAGE_SIZE 32768 -#endif // ENTT_PAGE_SIZE +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; -#ifndef ENTT_DISABLE_ASSERT -#include -#define ENTT_ASSERT(condition) assert(condition) -#else // ENTT_DISABLE_ASSERT -#define ENTT_ASSERT(...) ((void)0) -#endif // ENTT_DISABLE_ASSERT +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; -#endif // ENTT_CONFIG_CONFIG_H +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::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; -namespace entt { +/** + * @brief Wraps a static constant. + * @tparam Value A static constant. + */ +template +using integral_constant = std::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 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. + * @brief A class to use to push around lists of types, nothing more. + * @tparam Type Types provided by the type list. */ -template -class family { - inline static maybe_atomic_t identifier; +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); +}; - template - inline static const auto inner = identifier++; +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_element; -public: - /*! @brief Unsigned integer type. */ - using family_type = ENTT_ID_TYPE; +/** + * @brief Provides compile-time indexed access to the types of a type list. + * @tparam Index Index of the type to return. + * @tparam Type 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 Statically generated unique identifier for the given type. */ - template - // at the time I'm writing, clang crashes during compilation if auto is used in place of family_type here - inline static const family_type type = inner...>; +/** + * @brief Provides compile-time indexed access to the types of a type list. + * @tparam Type 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 = Type; }; +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; +/** + * @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 +constexpr type_list operator+(type_list, type_list) { + return {}; } +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_cat; -#endif // ENTT_CORE_FAMILY_HPP - -// #include "../core/algorithm.hpp" -#ifndef ENTT_CORE_ALGORITHM_HPP -#define ENTT_CORE_ALGORITHM_HPP - - -#include -#include -#include - - -namespace entt { - +/*! @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 Function object to wrap `std::sort` in a class type. - * - * Unfortunately, `std::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 `std::sort` in a - * function object. + * @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. */ -struct std_sort { - /** - * @brief Sorts the elements in a range. - * - * Sorts the elements in a range using the given binary comparison function. - * - * @tparam It Type of random access iterator. - * @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()(It first, It last, Compare compare = Compare{}, Args &&... args) const { - std::sort(std::forward(args)..., std::move(first), std::move(last), std::move(compare)); - } +template +struct type_list_cat, type_list, List...> { + /*! @brief A type list composed by the types of all the type lists. */ + using type = typename 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 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 It Type of random access iterator. - * @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()(It first, It last, Compare compare = Compare{}) const { - if(first != last) { - auto it = first + 1; - - while(it != last) { - auto pre = it++; - auto value = *pre; +/** + * @brief Helper type. + * @tparam List Type lists to concatenate. + */ +template +using type_list_cat_t = typename type_list_cat::type; - while(pre-- != first && compare(value, *pre)) { - *(pre+1) = *pre; - } +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; - *(pre+1) = value; - } - } - } +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; }; +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; -} - +/** + * @brief Helper type. + * @tparam Type A type list. + */ +template +using type_list_unique_t = typename type_list_unique::type; -#endif // ENTT_CORE_ALGORITHM_HPP +/** + * @brief Provides the member constant `value` 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; -// #include "../core/hashed_string.hpp" -#ifndef ENTT_CORE_HASHED_STRING_HPP -#define ENTT_CORE_HASHED_STRING_HPP +/** + * @copybrief type_list_contains + * @tparam Type Types provided by the type list. + * @tparam Other Type to look for. + */ +template +struct type_list_contains, Other>: std::disjunction...> {}; +/** + * @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; -#include -// #include "../config/config.h" +/*! @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 = typename type_list_diff::type; -namespace entt { +/** + * @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; /** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. + * @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 value. */ + static constexpr auto value = Value; +}; -namespace internal { - +/** + * @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; -template -struct fnv1a_traits; +/** + * @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 +constexpr 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 fnv1a_traits { - static constexpr std::uint32_t offset = 2166136261; - static constexpr std::uint32_t prime = 16777619; +struct value_list_cat<> { + /*! @brief A value list composed by the values of all the value lists. */ + using type = value_list<>; }; - -template<> -struct fnv1a_traits { - static constexpr std::uint64_t offset = 14695981039346656037ull; - static constexpr std::uint64_t prime = 1099511628211ull; +/** + * @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 = typename 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 = typename value_list_cat::type; +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::false_type {}; /** - * Internal details not to be documented. - * @endcond TURN_OFF_DOXYGEN + * @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>: std::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>: std::is_invocable {}; /** - * @brief Zero overhead unique identifier. - * - * A hashed string is a compile-time tool that allows users to use - * human-readable identifers 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. + * @brief Helper variable template. + * @tparam Func A valid function type. + * @tparam Args The list of arguments to use to probe the function type. */ -class hashed_string { - using traits_type = internal::fnv1a_traits; +template +inline constexpr bool is_applicable_v = is_applicable::value; - struct const_wrapper { - // non-explicit constructor on purpose - constexpr const_wrapper(const char *curr) ENTT_NOEXCEPT: str{curr} {} - const char *str; - }; +/*! @brief Same as std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::false_type {}; - // Fowler–Noll–Vo hash function v. 1a - the good - inline static constexpr ENTT_ID_TYPE helper(ENTT_ID_TYPE partial, const char *curr) ENTT_NOEXCEPT { - return curr[0] == 0 ? partial : helper((partial^curr[0])*traits_type::prime, curr+1); - } +/** + * @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>: std::is_invocable_r {}; -public: - /*! @brief Unsigned integer type. */ - using hash_type = ENTT_ID_TYPE; +/** + * @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 Returns directly the numeric representation of a string. - * - * Forcing template resolution avoids implicit conversions. An - * human-readable identifier can be anything but a plain, old bunch of - * characters.
- * Example of use: - * @code{.cpp} - * const auto value = hashed_string::to_value("my.png"); - * @endcode - * - * @tparam N Number of characters of the identifier. - * @param str Human-readable identifer. - * @return The numeric representation of the string. - */ - template - inline static constexpr hash_type to_value(const char (&str)[N]) ENTT_NOEXCEPT { - return helper(traits_type::offset, str); - } +/** + * @brief Provides the member constant `value` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; - /** - * @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. - */ - inline static hash_type to_value(const_wrapper wrapper) ENTT_NOEXCEPT { - return helper(traits_type::offset, wrapper.str); - } +/*! @copydoc is_complete */ +template +struct is_complete>: std::true_type {}; - /** - * @brief Returns directly the numeric representation of a string view. - * @param str Human-readable identifer. - * @param size Length of the string to hash. - * @return The numeric representation of the string. - */ - inline static hash_type to_value(const char *str, std::size_t size) ENTT_NOEXCEPT { - ENTT_ID_TYPE partial{traits_type::offset}; - while(size--) { partial = (partial^(str++)[0])*traits_type::prime; } - return partial; - } +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_complete_v = is_complete::value; - /*! @brief Constructs an empty hashed string. */ - constexpr hashed_string() ENTT_NOEXCEPT - : str{nullptr}, hash{} - {} +/** + * @brief Provides the member constant `value` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; - /** - * @brief Constructs a hashed string from an array of const chars. - * - * Forcing template resolution avoids implicit conversions. An - * human-readable identifier can be anything but a plain, old bunch of - * characters.
- * Example of use: - * @code{.cpp} - * hashed_string hs{"my.png"}; - * @endcode - * - * @tparam N Number of characters of the identifier. - * @param curr Human-readable identifer. - */ - template - constexpr hashed_string(const char (&curr)[N]) ENTT_NOEXCEPT - : str{curr}, hash{helper(traits_type::offset, curr)} - {} +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ - /** - * @brief Explicit constructor on purpose to avoid constructing a hashed - * string directly from a `const char *`. - * @param wrapper Helps achieving the purpose by relying on overloading. - */ - explicit constexpr hashed_string(const_wrapper wrapper) ENTT_NOEXCEPT - : str{wrapper.str}, hash{helper(traits_type::offset, wrapper.str)} - {} +namespace internal { - /** - * @brief Returns the human-readable representation of a hashed string. - * @return The string used to initialize the instance. - */ - constexpr const char * data() const ENTT_NOEXCEPT { - return str; - } +template +struct has_iterator_category: std::false_type {}; - /** - * @brief Returns the numeric representation of a hashed string. - * @return The numeric representation of the instance. - */ - constexpr hash_type value() const ENTT_NOEXCEPT { - return hash; - } +template +struct has_iterator_category::iterator_category>>: std::true_type {}; - /** - * @brief Returns the human-readable representation of a hashed string. - * @return The string used to initialize the instance. - */ - constexpr operator const char *() const ENTT_NOEXCEPT { return str; } +} // namespace internal - /*! @copydoc value */ - constexpr operator hash_type() const ENTT_NOEXCEPT { return hash; } - - /** - * @brief Compares two hashed strings. - * @param other Hashed string with which to compare. - * @return True if the two hashed strings are identical, false otherwise. - */ - constexpr bool operator==(const hashed_string &other) const ENTT_NOEXCEPT { - return hash == other.hash; - } - -private: - const char *str; - hash_type hash; -}; +/** + * Internal details not to be documented. + * @endcond + */ +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : internal::has_iterator_category {}; /** - * @brief Compares two hashed strings. - * @param lhs A valid hashed string. - * @param rhs A valid hashed string. - * @return True if the two hashed strings are identical, false otherwise. + * @brief Helper variable template. + * @tparam Type The type to test. */ -constexpr bool operator!=(const hashed_string &lhs, const hashed_string &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); -} - - -} - +template +inline constexpr bool is_iterator_v = is_iterator::value; /** - * @brief User defined literal for hashed strings. - * @param str The literal without its suffix. - * @return A properly initialized hashed string. + * @brief Provides the member constant `value` to true if a given type is both + * an empty and non-final class, false otherwise. + * @tparam Type The type to test */ -constexpr entt::hashed_string operator"" ENTT_HS_SUFFIX(const char *str, std::size_t) ENTT_NOEXCEPT { - return entt::hashed_string{str}; -} - +template +struct is_ebco_eligible + : std::conjunction, std::negation>> {}; -#endif // ENTT_CORE_HASHED_STRING_HPP +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_ebco_eligible_v = is_ebco_eligible::value; -// #include "../core/type_traits.hpp" -#ifndef ENTT_CORE_TYPE_TRAITS_HPP -#define ENTT_CORE_TYPE_TRAITS_HPP +/** + * @brief Provides the member constant `value` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; -#include -// #include "../core/hashed_string.hpp" -#ifndef ENTT_CORE_HASHED_STRING_HPP -#define ENTT_CORE_HASHED_STRING_HPP +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; -#include -// #include "../config/config.h" -#ifndef ENTT_CONFIG_CONFIG_H -#define ENTT_CONFIG_CONFIG_H +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ +namespace internal { -#ifndef ENTT_NOEXCEPT -#define ENTT_NOEXCEPT noexcept -#endif // ENTT_NOEXCEPT +template +struct has_tuple_size_value: std::false_type {}; +template +struct has_tuple_size_value::value)>>: std::true_type {}; -#ifndef ENTT_HS_SUFFIX -#define ENTT_HS_SUFFIX _hs -#endif // ENTT_HS_SUFFIX +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} -#ifndef ENTT_NO_ATOMIC -#include template -using maybe_atomic_t = std::atomic; -#else // ENTT_NO_ATOMIC +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; + } +} + template -using maybe_atomic_t = Type; -#endif // ENTT_NO_ATOMIC +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); + } +} +} // namespace internal -#ifndef ENTT_ID_TYPE -#include -#define ENTT_ID_TYPE std::uint32_t -#endif // ENTT_ID_TYPE +/** + * Internal details not to be documented. + * @endcond + */ +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; -#ifndef ENTT_PAGE_SIZE -#define ENTT_PAGE_SIZE 32768 -#endif // ENTT_PAGE_SIZE +/** + * @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 = std::remove_const_t; +}; -#ifndef ENTT_DISABLE_ASSERT -#include -#define ENTT_ASSERT(condition) assert(condition) -#else // ENTT_DISABLE_ASSERT -#define ENTT_ASSERT(...) ((void)0) -#endif // ENTT_DISABLE_ASSERT +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; +/** + * @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 = typename constness_as::type; -#endif // ENTT_CONFIG_CONFIG_H +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); + template + static Class *clazz(Ret (Class::*)(Args...)); + template + static Class *clazz(Ret (Class::*)(Args...) const); -namespace entt { + template + static Class *clazz(Type Class::*); +public: + /*! @brief The class of the given non-static member object or function. */ + using type = std::remove_pointer_t()))>; +}; /** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. */ +template +using member_class_t = typename member_class::type; +} // namespace entt -namespace internal { +#endif +// #include "fwd.hpp" +#ifndef ENTT_CONTAINER_FWD_HPP +#define ENTT_CONTAINER_FWD_HPP -template -struct fnv1a_traits; +#include +#include +namespace entt { -template<> -struct fnv1a_traits { - static constexpr std::uint32_t offset = 2166136261; - static constexpr std::uint32_t prime = 16777619; -}; +template< + typename Key, + typename Type, + typename = std::hash, + typename = std::equal_to, + typename = std::allocator>> +class dense_map; +template< + typename Type, + typename = std::hash, + typename = std::equal_to, + typename = std::allocator> +class dense_set; -template<> -struct fnv1a_traits { - static constexpr std::uint64_t offset = 14695981039346656037ull; - static constexpr std::uint64_t prime = 1099511628211ull; -}; +} // namespace entt +#endif -} +namespace entt { /** + * @cond TURN_OFF_DOXYGEN * Internal details not to be documented. - * @endcond TURN_OFF_DOXYGEN */ +namespace internal { -/** - * @brief Zero overhead unique identifier. - * - * A hashed string is a compile-time tool that allows users to use - * human-readable identifers 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. - */ -class hashed_string { - using traits_type = internal::fnv1a_traits; +template +struct dense_map_node final { + using value_type = std::pair; - struct const_wrapper { - // non-explicit constructor on purpose - constexpr const_wrapper(const char *curr) ENTT_NOEXCEPT: str{curr} {} - const char *str; - }; + template + dense_map_node(const std::size_t pos, Args &&...args) + : next{pos}, + element{std::forward(args)...} {} + + template + dense_map_node(std::allocator_arg_t, const Allocator &allocator, const std::size_t pos, Args &&...args) + : next{pos}, + element{entt::make_obj_using_allocator(allocator, std::forward(args)...)} {} + + template + dense_map_node(std::allocator_arg_t, const Allocator &allocator, const dense_map_node &other) + : next{other.next}, + element{entt::make_obj_using_allocator(allocator, other.element)} {} + + template + dense_map_node(std::allocator_arg_t, const Allocator &allocator, dense_map_node &&other) + : next{other.next}, + element{entt::make_obj_using_allocator(allocator, std::move(other.element))} {} + + std::size_t next; + value_type element; +}; - // Fowler–Noll–Vo hash function v. 1a - the good - inline static constexpr ENTT_ID_TYPE helper(ENTT_ID_TYPE partial, const char *curr) ENTT_NOEXCEPT { - return curr[0] == 0 ? partial : helper((partial^curr[0])*traits_type::prime, curr+1); - } +template +class dense_map_iterator final { + template + friend class dense_map_iterator; + + using first_type = decltype(std::as_const(std::declval()->element.first)); + using second_type = decltype((std::declval()->element.second)); public: - /*! @brief Unsigned integer type. */ - using hash_type = ENTT_ID_TYPE; + using value_type = std::pair; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; - /** - * @brief Returns directly the numeric representation of a string. - * - * Forcing template resolution avoids implicit conversions. An - * human-readable identifier can be anything but a plain, old bunch of - * characters.
- * Example of use: - * @code{.cpp} - * const auto value = hashed_string::to_value("my.png"); - * @endcode - * - * @tparam N Number of characters of the identifier. - * @param str Human-readable identifer. - * @return The numeric representation of the string. - */ - template - inline static constexpr hash_type to_value(const char (&str)[N]) ENTT_NOEXCEPT { - return helper(traits_type::offset, str); + dense_map_iterator() ENTT_NOEXCEPT + : it{} {} + + dense_map_iterator(const It iter) ENTT_NOEXCEPT + : it{iter} {} + + template && std::is_constructible_v>> + dense_map_iterator(const dense_map_iterator &other) ENTT_NOEXCEPT + : it{other.it} {} + + dense_map_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; } - /** - * @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. - */ - inline static hash_type to_value(const_wrapper wrapper) ENTT_NOEXCEPT { - return helper(traits_type::offset, wrapper.str); + dense_map_iterator operator++(int) ENTT_NOEXCEPT { + dense_map_iterator orig = *this; + return ++(*this), orig; } - /** - * @brief Returns directly the numeric representation of a string view. - * @param str Human-readable identifer. - * @param size Length of the string to hash. - * @return The numeric representation of the string. - */ - inline static hash_type to_value(const char *str, std::size_t size) ENTT_NOEXCEPT { - ENTT_ID_TYPE partial{traits_type::offset}; - while(size--) { partial = (partial^(str++)[0])*traits_type::prime; } - return partial; + dense_map_iterator &operator--() ENTT_NOEXCEPT { + return --it, *this; } - /*! @brief Constructs an empty hashed string. */ - constexpr hashed_string() ENTT_NOEXCEPT - : str{nullptr}, hash{} - {} + dense_map_iterator operator--(int) ENTT_NOEXCEPT { + dense_map_iterator orig = *this; + return operator--(), orig; + } - /** - * @brief Constructs a hashed string from an array of const chars. - * - * Forcing template resolution avoids implicit conversions. An - * human-readable identifier can be anything but a plain, old bunch of - * characters.
- * Example of use: - * @code{.cpp} - * hashed_string hs{"my.png"}; - * @endcode - * - * @tparam N Number of characters of the identifier. - * @param curr Human-readable identifer. - */ - template - constexpr hashed_string(const char (&curr)[N]) ENTT_NOEXCEPT - : str{curr}, hash{helper(traits_type::offset, curr)} - {} + dense_map_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + it += value; + return *this; + } - /** - * @brief Explicit constructor on purpose to avoid constructing a hashed - * string directly from a `const char *`. - * @param wrapper Helps achieving the purpose by relying on overloading. - */ - explicit constexpr hashed_string(const_wrapper wrapper) ENTT_NOEXCEPT - : str{wrapper.str}, hash{helper(traits_type::offset, wrapper.str)} - {} + dense_map_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + dense_map_iterator copy = *this; + return (copy += value); + } - /** - * @brief Returns the human-readable representation of a hashed string. - * @return The string used to initialize the instance. - */ - constexpr const char * data() const ENTT_NOEXCEPT { - return str; + dense_map_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); } - /** - * @brief Returns the numeric representation of a hashed string. - * @return The numeric representation of the instance. - */ - constexpr hash_type value() const ENTT_NOEXCEPT { - return hash; + dense_map_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); } - /** - * @brief Returns the human-readable representation of a hashed string. - * @return The string used to initialize the instance. - */ - constexpr operator const char *() const ENTT_NOEXCEPT { return str; } + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + return {it[value].element.first, it[value].element.second}; + } - /*! @copydoc value */ - constexpr operator hash_type() const ENTT_NOEXCEPT { return hash; } + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } - /** - * @brief Compares two hashed strings. - * @param other Hashed string with which to compare. - * @return True if the two hashed strings are identical, false otherwise. - */ - constexpr bool operator==(const hashed_string &other) const ENTT_NOEXCEPT { - return hash == other.hash; + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return {it->element.first, it->element.second}; } + template + friend std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator==(const dense_map_iterator &, const dense_map_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator<(const dense_map_iterator &, const dense_map_iterator &) ENTT_NOEXCEPT; + private: - const char *str; - hash_type hash; + It it; }; +template +[[nodiscard]] std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it - rhs.it; +} -/** - * @brief Compares two hashed strings. - * @param lhs A valid hashed string. - * @param rhs A valid hashed string. - * @return True if the two hashed strings are identical, false otherwise. - */ -constexpr bool operator!=(const hashed_string &lhs, const hashed_string &rhs) ENTT_NOEXCEPT { +template +[[nodiscard]] bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { return !(lhs == rhs); } +template +[[nodiscard]] bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it < rhs.it; +} +template +[[nodiscard]] bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return rhs < lhs; } +template +[[nodiscard]] bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} -/** - * @brief User defined literal for hashed strings. - * @param str The literal without its suffix. - * @return A properly initialized hashed string. - */ -constexpr entt::hashed_string operator"" ENTT_HS_SUFFIX(const char *str, std::size_t) ENTT_NOEXCEPT { - return entt::hashed_string{str}; +template +[[nodiscard]] bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); } +template +class dense_map_local_iterator final { + template + friend class dense_map_local_iterator; -#endif // ENTT_CORE_HASHED_STRING_HPP + using first_type = decltype(std::as_const(std::declval()->element.first)); + using second_type = decltype((std::declval()->element.second)); +public: + using value_type = std::pair; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + dense_map_local_iterator() ENTT_NOEXCEPT + : it{}, + offset{} {} -namespace entt { + dense_map_local_iterator(It iter, const std::size_t pos) ENTT_NOEXCEPT + : it{iter}, + offset{pos} {} + template && std::is_constructible_v>> + dense_map_local_iterator(const dense_map_local_iterator &other) ENTT_NOEXCEPT + : it{other.it}, + offset{other.offset} {} -/** - * @brief A class to use to push around lists of types, nothing more. - * @tparam Type Types provided by the given type list. - */ -template -struct type_list { - /*! @brief Unsigned integer type. */ - static constexpr auto size = sizeof...(Type); -}; + dense_map_local_iterator &operator++() ENTT_NOEXCEPT { + return offset = it[offset].next, *this; + } + dense_map_local_iterator operator++(int) ENTT_NOEXCEPT { + dense_map_local_iterator orig = *this; + return ++(*this), orig; + } -/*! @brief Primary template isn't defined on purpose. */ -template -struct type_list_cat; + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return {it[offset].element.first, it[offset].element.second}; + } -/*! @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<>; + [[nodiscard]] std::size_t index() const ENTT_NOEXCEPT { + return offset; + } + +private: + It it; + std::size_t offset; }; +template +[[nodiscard]] bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() == rhs.index(); +} -/** - * @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 = typename type_list_cat, List...>::type; -}; +template +[[nodiscard]] bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} +} // namespace internal /** - * @brief Concatenates multiple type lists. - * @tparam Type Types provided by the type list. + * Internal details not to be documented. + * @endcond */ -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. + * @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 -using type_list_cat_t = typename type_list_cat::type; +template +class dense_map { + static constexpr float default_threshold = 0.875f; + static constexpr std::size_t minimum_capacity = 8u; + using node_type = internal::dense_map_node; + using alloc_traits = typename std::allocator_traits; + static_assert(std::is_same_v>, "Invalid value type"); + using sparse_container_type = std::vector>; + using packed_container_type = std::vector>; -/*! @brief Primary template isn't defined on purpose. */ -template -struct type_list_unique; - - -/** - * @brief Removes duplicates types from a type list. - * @tparam Type One of the types provided by the given type list. - * @tparam Other The other types provided by the given type list. - */ -template -struct type_list_unique> { - /*! @brief A type list without duplicate types. */ - using type = std::conditional_t< - std::disjunction_v...>, - typename type_list_unique>::type, - type_list_cat_t, typename type_list_unique>::type> - >; -}; - + template + [[nodiscard]] std::size_t key_to_bucket(const Other &key) const ENTT_NOEXCEPT { + return fast_mod(sparse.second()(key), bucket_count()); + } -/*! @brief Removes duplicates types from a type list. */ -template<> -struct type_list_unique> { - /*! @brief A type list without duplicate types. */ - using type = type_list<>; -}; + template + [[nodiscard]] auto constrained_find(const Other &key, std::size_t bucket) { + for(auto it = begin(bucket), last = end(bucket); it != last; ++it) { + if(packed.second()(it->first, key)) { + return begin() + static_cast(it.index()); + } + } + return end(); + } -/** - * @brief Helper type. - * @tparam Type A type list. - */ -template -using type_list_unique_t = typename type_list_unique::type; + template + [[nodiscard]] auto constrained_find(const Other &key, std::size_t bucket) const { + for(auto it = cbegin(bucket), last = cend(bucket); it != last; ++it) { + if(packed.second()(it->first, key)) { + return cbegin() + static_cast(it.index()); + } + } + return cend(); + } -/*! @brief Traits class used mainly to push things across boundaries. */ -template -struct named_type_traits; + 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 std::make_pair(it, false); + } -/** - * @brief Specialization used to get rid of constness. - * @tparam Type Named type. - */ -template -struct named_type_traits - : named_type_traits -{}; + packed.first().emplace_back(sparse.first()[index], std::piecewise_construct, std::forward_as_tuple(std::forward(key)), std::forward_as_tuple(std::forward(args)...)); + sparse.first()[index] = packed.first().size() - 1u; + rehash_if_required(); + return std::make_pair(--end(), true); + } -/** - * @brief Helper type. - * @tparam Type Potentially named type. - */ -template -using named_type_traits_t = typename named_type_traits::type; + 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 = std::forward(value); + return std::make_pair(it, false); + } -/** - * @brief Provides the member constant `value` to true if a given type has a - * name. In all other cases, `value` is false. - */ -template> -struct is_named_type: std::false_type {}; + packed.first().emplace_back(sparse.first()[index], std::forward(key), std::forward(value)); + sparse.first()[index] = packed.first().size() - 1u; + rehash_if_required(); + return std::make_pair(--end(), true); + } -/** - * @brief Provides the member constant `value` to true if a given type has a - * name. In all other cases, `value` is false. - * @tparam Type Potentially named type. - */ -template -struct is_named_type>>>: std::true_type {}; + void move_and_pop(const std::size_t pos) { + if(const auto last = size() - 1u; pos != last) { + packed.first()[pos] = std::move(packed.first().back()); + size_type *curr = sparse.first().data() + key_to_bucket(packed.first().back().element.first); + for(; *curr != last; curr = &packed.first()[*curr].next) {} + *curr = pos; + } + packed.first().pop_back(); + } -/** - * @brief Helper variable template. - * - * True if a given type has a name, false otherwise. - * - * @tparam Type Potentially named type. - */ -template -constexpr auto is_named_type_v = is_named_type::value; + void rehash_if_required() { + if(size() > (bucket_count() * max_load_factor())) { + rehash(bucket_count() * 2u); + } + } +public: + /*! @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 = std::pair; + /*! @brief Unsigned integer type. */ + using size_type = std::size_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 Allocator type. */ + using allocator_type = Allocator; + /*! @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 Utility macro to deal with an issue of MSVC. - * - * See _msvc-doesnt-expand-va-args-correctly_ on SO for all the details. - * - * @param args Argument to expand. - */ -#define ENTT_EXPAND(args) args + /** + * @brief Constructs an empty container with a given allocator and user + * supplied minimal number of buckets. + * @param bucket_count Minimal number of buckets. + * @param allocator The allocator to use. + */ + dense_map(const size_type bucket_count, const allocator_type &allocator) + : dense_map{bucket_count, hasher{}, key_equal{}, allocator} {} + /** + * @brief Constructs an empty container with a given allocator, hash + * function and user supplied minimal number of buckets. + * @param bucket_count Minimal number of buckets. + * @param hash Hash function to use. + * @param allocator The allocator to use. + */ + dense_map(const size_type bucket_count, const hasher &hash, const allocator_type &allocator) + : dense_map{bucket_count, hash, key_equal{}, allocator} {} -/** - * @brief Makes an already existing type a named type. - * @param type Type to assign a name to. - */ -#define ENTT_NAMED_TYPE(type)\ - template<>\ - struct entt::named_type_traits\ - : std::integral_constant\ - {\ - static_assert(std::is_same_v, type>);\ - }; + /** + * @brief Constructs an empty container with a given allocator, hash + * function, compare function and user supplied minimal number of buckets. + * @param bucket_count 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 bucket_count, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{}) + : sparse{allocator, hash}, + packed{allocator, equal}, + threshold{default_threshold} { + rehash(bucket_count); + } + /*! @brief Default copy constructor. */ + dense_map(const dense_map &) = default; -/** - * @brief Defines a named type (to use for structs). - * @param clazz Name of the type to define. - * @param body Body of the type to define. - */ -#define ENTT_NAMED_STRUCT_ONLY(clazz, body)\ - struct clazz body;\ - ENTT_NAMED_TYPE(clazz) + /** + * @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{std::piecewise_construct, std::forward_as_tuple(other.sparse.first(), allocator), std::forward_as_tuple(other.sparse.second())}, + packed{std::piecewise_construct, std::forward_as_tuple(other.packed.first(), allocator), std::forward_as_tuple(other.packed.second())}, + threshold{other.threshold} {} + /*! @brief Default move constructor. */ + dense_map(dense_map &&) = default; -/** - * @brief Defines a named type (to use for structs). - * @param ns Namespace where to define the named type. - * @param clazz Name of the type to define. - * @param body Body of the type to define. - */ -#define ENTT_NAMED_STRUCT_WITH_NAMESPACE(ns, clazz, body)\ - namespace ns { struct clazz body; }\ - ENTT_NAMED_TYPE(ns::clazz) + /** + * @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{std::piecewise_construct, std::forward_as_tuple(std::move(other.sparse.first()), allocator), std::forward_as_tuple(std::move(other.sparse.second()))}, + packed{std::piecewise_construct, std::forward_as_tuple(std::move(other.packed.first()), allocator), std::forward_as_tuple(std::move(other.packed.second()))}, + threshold{other.threshold} {} + /** + * @brief Default copy assignment operator. + * @return This container. + */ + dense_map &operator=(const dense_map &) = default; -/*! @brief Utility function to simulate macro overloading. */ -#define ENTT_NAMED_STRUCT_OVERLOAD(_1, _2, _3, FUNC, ...) FUNC -/*! @brief Defines a named type (to use for structs). */ -#define ENTT_NAMED_STRUCT(...) ENTT_EXPAND(ENTT_NAMED_STRUCT_OVERLOAD(__VA_ARGS__, ENTT_NAMED_STRUCT_WITH_NAMESPACE, ENTT_NAMED_STRUCT_ONLY,)(__VA_ARGS__)) + /** + * @brief Default move assignment operator. + * @return This container. + */ + dense_map &operator=(dense_map &&) = default; + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return sparse.first().get_allocator(); + } -/** - * @brief Defines a named type (to use for classes). - * @param clazz Name of the type to define. - * @param body Body of the type to define. - */ -#define ENTT_NAMED_CLASS_ONLY(clazz, body)\ - class clazz body;\ - ENTT_NAMED_TYPE(clazz) + /** + * @brief Returns an iterator to the beginning. + * + * The returned iterator points to the first instance of the internal array. + * 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 ENTT_NOEXCEPT { + return packed.first().begin(); + } + /*! @copydoc cbegin */ + [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT { + return cbegin(); + } -/** - * @brief Defines a named type (to use for classes). - * @param ns Namespace where to define the named type. - * @param clazz Name of the type to define. - * @param body Body of the type to define. - */ -#define ENTT_NAMED_CLASS_WITH_NAMESPACE(ns, clazz, body)\ - namespace ns { class clazz body; }\ - ENTT_NAMED_TYPE(ns::clazz) + /*! @copydoc begin */ + [[nodiscard]] iterator begin() ENTT_NOEXCEPT { + return packed.first().begin(); + } + /** + * @brief Returns an iterator to the end. + * + * The returned iterator points to the element following the last instance + * of the internal array. Attempting to dereference the returned iterator + * results in undefined behavior. + * + * @return An iterator to the element following the last instance of the + * internal array. + */ + [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { + return packed.first().end(); + } -/*! @brief Utility function to simulate macro overloading. */ -#define ENTT_NAMED_CLASS_MACRO(_1, _2, _3, FUNC, ...) FUNC -/*! @brief Defines a named type (to use for classes). */ -#define ENTT_NAMED_CLASS(...) ENTT_EXPAND(ENTT_NAMED_CLASS_MACRO(__VA_ARGS__, ENTT_NAMED_CLASS_WITH_NAMESPACE, ENTT_NAMED_CLASS_ONLY,)(__VA_ARGS__)) + /*! @copydoc cend */ + [[nodiscard]] const_iterator end() const ENTT_NOEXCEPT { + return cend(); + } + /*! @copydoc end */ + [[nodiscard]] iterator end() ENTT_NOEXCEPT { + return packed.first().end(); + } -#endif // ENTT_CORE_TYPE_TRAITS_HPP + /** + * @brief Checks whether a container is empty. + * @return True if the container is empty, false otherwise. + */ + [[nodiscard]] bool empty() const ENTT_NOEXCEPT { + return packed.first().empty(); + } -// #include "../signal/sigh.hpp" -#ifndef ENTT_SIGNAL_SIGH_HPP -#define ENTT_SIGNAL_SIGH_HPP + /** + * @brief Returns the number of elements in a container. + * @return Number of elements in a container. + */ + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return packed.first().size(); + } + /*! @brief Clears the container. */ + void clear() ENTT_NOEXCEPT { + sparse.first().clear(); + packed.first().clear(); + rehash(0u); + } -#include -#include -#include -#include -#include -// #include "../config/config.h" -#ifndef ENTT_CONFIG_CONFIG_H -#define ENTT_CONFIG_CONFIG_H + /** + * @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. + */ + std::pair insert(const value_type &value) { + return insert_or_do_nothing(value.first, value.second); + } + /*! @copydoc insert */ + std::pair insert(value_type &&value) { + return insert_or_do_nothing(std::move(value.first), std::move(value.second)); + } -#ifndef ENTT_NOEXCEPT -#define ENTT_NOEXCEPT noexcept -#endif // ENTT_NOEXCEPT + /** + * @copydoc insert + * @tparam Arg Type of the key-value pair to insert into the container. + */ + template + std::enable_if_t, std::pair> + insert(Arg &&value) { + return insert_or_do_nothing(std::forward(value).first, std::forward(value).second); + } + /** + * @brief Inserts elements into the container, if their keys do not exist. + * @tparam It Type of input iterator. + * @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. + */ + template + void insert(It first, It last) { + for(; first != last; ++first) { + insert(*first); + } + } -#ifndef ENTT_HS_SUFFIX -#define ENTT_HS_SUFFIX _hs -#endif // ENTT_HS_SUFFIX + /** + * @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 + std::pair insert_or_assign(const key_type &key, Arg &&value) { + return insert_or_overwrite(key, std::forward(value)); + } + /*! @copydoc insert_or_assign */ + template + std::pair insert_or_assign(key_type &&key, Arg &&value) { + return insert_or_overwrite(std::move(key), std::forward(value)); + } -#ifndef ENTT_NO_ATOMIC -#include -template -using maybe_atomic_t = std::atomic; -#else // ENTT_NO_ATOMIC -template -using maybe_atomic_t = Type; -#endif // ENTT_NO_ATOMIC + /** + * @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 + std::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(std::forward(args).first..., std::forward(args).second...); + } else if constexpr(sizeof...(Args) == 2u) { + return insert_or_do_nothing(std::forward(args)...); + } else { + auto &node = packed.first().emplace_back(packed.first().size(), std::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 std::make_pair(it, false); + } -#ifndef ENTT_ID_TYPE -#include -#define ENTT_ID_TYPE std::uint32_t -#endif // ENTT_ID_TYPE + std::swap(node.next, sparse.first()[index]); + rehash_if_required(); + return std::make_pair(--end(), true); + } + } -#ifndef ENTT_PAGE_SIZE -#define ENTT_PAGE_SIZE 32768 -#endif // ENTT_PAGE_SIZE + /** + * @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 + std::pair try_emplace(const key_type &key, Args &&...args) { + return insert_or_do_nothing(key, std::forward(args)...); + } + /*! @copydoc try_emplace */ + template + std::pair try_emplace(key_type &&key, Args &&...args) { + return insert_or_do_nothing(std::move(key), std::forward(args)...); + } -#ifndef ENTT_DISABLE_ASSERT -#include -#define ENTT_ASSERT(condition) assert(condition) -#else // ENTT_DISABLE_ASSERT -#define ENTT_ASSERT(...) ((void)0) -#endif // ENTT_DISABLE_ASSERT + /** + * @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(); -#endif // ENTT_CONFIG_CONFIG_H + for(auto from = last - cbegin(); from != dist; --from) { + erase(packed.first()[from - 1u].element.first); + } -// #include "delegate.hpp" -#ifndef ENTT_SIGNAL_DELEGATE_HPP -#define ENTT_SIGNAL_DELEGATE_HPP + 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().data() + key_to_bucket(key); *curr != (std::numeric_limits::max)(); 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; + } + } -#include -#include -#include -#include -// #include "../config/config.h" + return 0u; + } + /** + * @brief Exchanges the contents with those of a given container. + * @param other Container to exchange the content with. + */ + void swap(dense_map &other) { + using std::swap; + swap(sparse, other.sparse); + swap(packed, other.packed); + swap(threshold, other.threshold); + } + /** + * @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; + } -namespace entt { + /*! @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 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; + } -/** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. - */ + /** + * @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(std::move(key)).first->second; + } + /** + * @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)); + } -namespace internal { + /*! @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 + * value. + * @tparam Other Type of the key value of an element to search for. + * @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. + */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + find(const Other &key) { + return constrained_find(key, key_to_bucket(key)); + } -template -auto to_function_pointer(Ret(*)(Args...)) -> Ret(*)(Args...); + /*! @copydoc find */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + find(const Other &key) const { + return constrained_find(key, key_to_bucket(key)); + } + /** + * @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()); + } -template -auto to_function_pointer(Ret(*)(Type *, Args...), Type *) -> Ret(*)(Args...); + /** + * @brief Checks if the container contains an element with a key that + * compares _equivalent_ to a given value. + * @tparam Other Type of the key value of an element to search for. + * @param key Key value of an element to search for. + * @return True if there is such an element, false otherwise. + */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + contains(const Other &key) const { + 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().begin(), sparse.first()[index]}; + } -template -auto to_function_pointer(Ret(Class:: *)(Args...), Class *) -> Ret(*)(Args...); + /** + * @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().begin(), sparse.first()[index]}; + } -template -auto to_function_pointer(Ret(Class:: *)(Args...) const, Class *) -> Ret(*)(Args...); - - -} - - -/** - * Internal details not to be documented. - * @endcond TURN_OFF_DOXYGEN - */ - - -/*! @brief Used to wrap a function or a member of a specified type. */ -template -struct connect_arg_t {}; - - -/*! @brief Constant of type connect_arg_t used to disambiguate calls. */ -template -constexpr connect_arg_t connect_arg{}; - - -/** - * @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 general purpose invoker with no memory overhead for - * free functions (with or without payload) and members provided along with an - * instance on which to invoke them. - * - * @tparam Ret Return type of a function type. - * @tparam Args Types of arguments of a function type. - */ -template -class delegate { - using proto_fn_type = Ret(const void *, Args...); - -public: - /*! @brief Function type of the delegate. */ - using function_type = Ret(Args...); - - /*! @brief Default constructor. */ - delegate() ENTT_NOEXCEPT - : fn{nullptr}, data{nullptr} - {} + /** + * @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 {packed.first().begin(), (std::numeric_limits::max)()}; + } /** - * @brief Constructs a delegate and connects a free function to it. - * @tparam Function A valid free function pointer. + * @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. */ - template - delegate(connect_arg_t) ENTT_NOEXCEPT - : delegate{} - { - connect(); + [[nodiscard]] const_local_iterator end([[maybe_unused]] const size_type index) const { + return cend(index); } /** - * @brief Constructs a delegate and connects a member for a given instance - * or a free function with payload. - * @tparam Candidate Member or free function 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. + * @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. */ - template - delegate(connect_arg_t, Type *value_or_instance) ENTT_NOEXCEPT - : delegate{} - { - connect(value_or_instance); + [[nodiscard]] local_iterator end([[maybe_unused]] const size_type index) { + return {packed.first().begin(), (std::numeric_limits::max)()}; } /** - * @brief Connects a free function to a delegate. - * @tparam Function A valid free function pointer. + * @brief Returns the number of buckets. + * @return The number of buckets. */ - template - void connect() ENTT_NOEXCEPT { - static_assert(std::is_invocable_r_v); - data = nullptr; + [[nodiscard]] size_type bucket_count() const { + return sparse.first().size(); + } - fn = [](const void *, Args... args) -> Ret { - // this allows void(...) to eat return values and avoid errors - return Ret(std::invoke(Function, args...)); - }; + /** + * @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 Connects a member function for a given instance or a free function - * with payload 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 Member or free function 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. + * @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. */ - template - void connect(Type *value_or_instance) ENTT_NOEXCEPT { - static_assert(std::is_invocable_r_v); - data = value_or_instance; + [[nodiscard]] size_type bucket_size(const size_type index) const { + return static_cast(std::distance(begin(index), end(index))); + } - fn = [](const void *payload, Args... args) -> Ret { - Type *curr = nullptr; + /** + * @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); + } - if constexpr(std::is_const_v) { - curr = static_cast(payload); - } else { - curr = static_cast(const_cast(payload)); - } + /** + * @brief Returns the average number of elements per bucket. + * @return The average number of elements per bucket. + */ + [[nodiscard]] float load_factor() const { + return size() / static_cast(bucket_count()); + } - // this allows void(...) to eat return values and avoid errors - return Ret(std::invoke(Candidate, curr, args...)); - }; + /** + * @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 Resets a delegate. - * - * After a reset, a delegate cannot be invoked anymore. + * @brief Sets the desired maximum average number of elements per bucket. + * @param value A desired maximum average number of elements per bucket. */ - void reset() ENTT_NOEXCEPT { - fn = nullptr; - data = nullptr; + void max_load_factor(const float value) { + ENTT_ASSERT(value > 0.f, "Invalid load factor"); + threshold = value; + rehash(0u); } /** - * @brief Returns the instance linked to a delegate, if any. - * @return An opaque pointer to the instance linked to the delegate, if any. + * @brief Reserves at least the specified number of buckets and regenerates + * the hash table. + * @param count New number of buckets. */ - const void * instance() const ENTT_NOEXCEPT { - return data; + void rehash(const size_type count) { + auto value = (std::max)(count, minimum_capacity); + value = (std::max)(value, static_cast(size() / max_load_factor())); + + if(const auto sz = next_power_of_two(value); sz != bucket_count()) { + sparse.first().resize(sz); + std::fill(sparse.first().begin(), sparse.first().end(), (std::numeric_limits::max)()); + + for(size_type pos{}, last = size(); pos < last; ++pos) { + const auto index = key_to_bucket(packed.first()[pos].element.first); + packed.first()[pos].next = std::exchange(sparse.first()[index], pos); + } + } } /** - * @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.
- * An assertion will abort the execution at runtime in debug mode if the - * delegate has not yet been set. - * - * @param args Arguments to use to invoke the underlying function. - * @return The value returned by the underlying function. + * @brief Reserves space for at least the specified number of elements and + * regenerates the hash table. + * @param count New number of elements. */ - Ret operator()(Args... args) const { - ENTT_ASSERT(fn); - return fn(data, args...); + void reserve(const size_type count) { + packed.first().reserve(count); + rehash(static_cast(std::ceil(count / max_load_factor()))); } /** - * @brief Checks whether a delegate actually stores a listener. - * @return False if the delegate is empty, true otherwise. + * @brief Returns the function used to hash the keys. + * @return The function used to hash the keys. */ - explicit operator bool() const ENTT_NOEXCEPT { - // no need to test also data - return fn; + [[nodiscard]] hasher hash_function() const { + return sparse.second(); } /** - * @brief Checks if the connected functions differ. - * - * Instances connected to delegates are ignored by this operator. Use the - * `instance` member function instead. - * - * @param other Delegate with which to compare. - * @return False if the connected functions differ, true otherwise. + * @brief Returns the function used to compare keys for equality. + * @return The function used to compare keys for equality. */ - bool operator==(const delegate &other) const ENTT_NOEXCEPT { - return fn == other.fn; + [[nodiscard]] key_equal key_eq() const { + return packed.second(); } private: - proto_fn_type *fn; - const void *data; + compressed_pair sparse; + compressed_pair packed; + float threshold; }; +} // namespace entt /** - * @brief Checks if the connected functions differ. - * - * Instances connected to delegates are ignored by this operator. Use the - * `instance` member function instead. - * - * @tparam Ret Return type of a function type. - * @tparam Args Types of arguments of a function type. - * @param lhs A valid delegate object. - * @param rhs A valid delegate object. - * @return True if the connected functions differ, false otherwise. + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. */ -template -bool operator!=(const delegate &lhs, const delegate &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); -} +namespace std { -/** - * @brief Deduction guide. - * - * It allows to deduce the function type of the delegate directly from a - * function provided to the constructor. - * - * @tparam Function A valid free function pointer. - */ -template -delegate(connect_arg_t) ENTT_NOEXCEPT --> delegate>; +template +struct uses_allocator, Allocator> + : std::true_type {}; +} // namespace std /** - * @brief Deduction guide. - * - * It allows to deduce the function type of the delegate directly from a member - * or a free function with payload provided to the constructor. - * - * @tparam Candidate Member or free function to connect to the delegate. - * @tparam Type Type of class or type of payload. + * Internal details not to be documented. + * @endcond */ -template -delegate(connect_arg_t, Type *) ENTT_NOEXCEPT --> delegate()))>>; - -} +#endif +// #include "container/dense_set.hpp" +#ifndef ENTT_CONTAINER_DENSE_SET_HPP +#define ENTT_CONTAINER_DENSE_SET_HPP -#endif // ENTT_SIGNAL_DELEGATE_HPP +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" -// #include "fwd.hpp" -#ifndef ENTT_SIGNAL_FWD_HPP -#define ENTT_SIGNAL_FWD_HPP +// #include "../core/compressed_pair.hpp" +// #include "../core/memory.hpp" -// #include "../config/config.h" +// #include "../core/type_traits.hpp" +// #include "fwd.hpp" namespace entt { +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ -/*! @class delegate */ -template -class delegate; +namespace internal { -/*! @class sink */ -template -class sink; +template +class dense_set_iterator final { + template + friend class dense_set_iterator; -/*! @class sigh */ -template -struct sigh; +public: + using value_type = typename It::value_type::second_type; + using pointer = const value_type *; + using reference = const value_type &; + using difference_type = std::ptrdiff_t; + using iterator_category = std::random_access_iterator_tag; + dense_set_iterator() ENTT_NOEXCEPT + : it{} {} -} + dense_set_iterator(const It iter) ENTT_NOEXCEPT + : it{iter} {} + template && std::is_constructible_v>> + dense_set_iterator(const dense_set_iterator &other) ENTT_NOEXCEPT + : it{other.it} {} -#endif // ENTT_SIGNAL_FWD_HPP + dense_set_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } + dense_set_iterator operator++(int) ENTT_NOEXCEPT { + dense_set_iterator orig = *this; + return ++(*this), orig; + } + dense_set_iterator &operator--() ENTT_NOEXCEPT { + return --it, *this; + } -namespace entt { + dense_set_iterator operator--(int) ENTT_NOEXCEPT { + dense_set_iterator orig = *this; + return operator--(), orig; + } + dense_set_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + it += value; + return *this; + } -/** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. - */ + dense_set_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + dense_set_iterator copy = *this; + return (copy += value); + } + dense_set_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); + } -namespace internal { + dense_set_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); + } + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + return it[value].second; + } -template -struct invoker; + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return std::addressof(it->second); + } + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return *operator->(); + } -template -struct invoker { - virtual ~invoker() = default; + template + friend std::ptrdiff_t operator-(const dense_set_iterator &, const dense_set_iterator &) ENTT_NOEXCEPT; - bool invoke(Collector &collector, const delegate &delegate, Args... args) const { - return collector(delegate(args...)); - } + template + friend bool operator==(const dense_set_iterator &, const dense_set_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator<(const dense_set_iterator &, const dense_set_iterator &) ENTT_NOEXCEPT; + +private: + It it; }; +template +[[nodiscard]] std::ptrdiff_t operator-(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it - rhs.it; +} -template -struct invoker { - virtual ~invoker() = default; +template +[[nodiscard]] bool operator==(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} - bool invoke(Collector &, const delegate &delegate, Args... args) const { - return (delegate(args...), true); - } -}; +template +[[nodiscard]] bool operator!=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +[[nodiscard]] bool operator<(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it < rhs.it; +} +template +[[nodiscard]] bool operator>(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} -template -struct null_collector { - using result_type = Ret; - bool operator()(result_type) const ENTT_NOEXCEPT { return true; } -}; +template +[[nodiscard]] bool operator<=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} +template +[[nodiscard]] bool operator>=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} -template<> -struct null_collector { - using result_type = void; - bool operator()() const ENTT_NOEXCEPT { return true; } -}; +template +class dense_set_local_iterator final { + template + friend class dense_set_local_iterator; +public: + using value_type = typename It::value_type::second_type; + using pointer = const value_type *; + using reference = const value_type &; + using difference_type = std::ptrdiff_t; + using iterator_category = std::forward_iterator_tag; -template -struct default_collector; + dense_set_local_iterator() ENTT_NOEXCEPT + : it{}, + offset{} {} + dense_set_local_iterator(It iter, const std::size_t pos) ENTT_NOEXCEPT + : it{iter}, + offset{pos} {} -template -struct default_collector { - using collector_type = null_collector; -}; + template && std::is_constructible_v>> + dense_set_local_iterator(const dense_set_local_iterator &other) ENTT_NOEXCEPT + : it{other.it}, + offset{other.offset} {} + dense_set_local_iterator &operator++() ENTT_NOEXCEPT { + return offset = it[offset].first, *this; + } -template -using default_collector_type = typename default_collector::collector_type; + dense_set_local_iterator operator++(int) ENTT_NOEXCEPT { + dense_set_local_iterator orig = *this; + return ++(*this), orig; + } + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return std::addressof(it[offset].second); + } -} + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return *operator->(); + } + [[nodiscard]] std::size_t index() const ENTT_NOEXCEPT { + return offset; + } -/** - * Internal details not to be documented. - * @endcond TURN_OFF_DOXYGEN - */ +private: + It it; + std::size_t offset; +}; +template +[[nodiscard]] bool operator==(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() == rhs.index(); +} -/** - * @brief Sink implementation. - * - * Primary template isn't defined on purpose. All the specializations give a - * compile-time error unless the template parameter is a function type. - * - * @tparam Function A valid function type. - */ -template -class sink; +template +[[nodiscard]] bool operator!=(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} +} // namespace internal /** - * @brief Unmanaged signal handler declaration. - * - * Primary template isn't defined on purpose. All the specializations give a - * compile-time error unless the template parameter is a function type. - * - * @tparam Function A valid function type. - * @tparam Collector Type of collector to use, if any. + * Internal details not to be documented. + * @endcond */ -template> -struct sigh; - /** - * @brief Sink implementation. + * @brief Associative container for unique objects of a given type. * - * A sink is an opaque object used to connect listeners to signals.
- * 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 a class. + * 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 Ret Return type of a function type. - * @tparam Args Types of arguments of a function type. + * @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 sink { - /*! @brief A signal is allowed to create sinks. */ - template - friend struct sigh; +template +class dense_set { + static constexpr float default_threshold = 0.875f; + static constexpr std::size_t minimum_capacity = 8u; - template - Type * payload_type(Ret(*)(Type *, Args...)); + using node_type = std::pair; + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using sparse_container_type = std::vector>; + using packed_container_type = std::vector>; - sink(std::vector> *ref) ENTT_NOEXCEPT - : calls{ref} - {} + template + [[nodiscard]] std::size_t value_to_bucket(const Other &value) const ENTT_NOEXCEPT { + return fast_mod(sparse.second()(value), bucket_count()); + } -public: - /** - * @brief Returns false if at least a listener is connected to the sink. - * @return True if the sink has no listeners connected, false otherwise. - */ - bool empty() const ENTT_NOEXCEPT { - return calls->empty(); + template + [[nodiscard]] auto constrained_find(const Other &value, std::size_t bucket) { + for(auto it = begin(bucket), last = end(bucket); it != last; ++it) { + if(packed.second()(*it, value)) { + return begin() + static_cast(it.index()); + } + } + + return end(); } - /** - * @brief Connects a free function to a signal. - * - * The signal handler performs checks to avoid multiple connections for free - * functions. - * - * @tparam Function A valid free function pointer. - */ - template - void connect() { - disconnect(); - delegate delegate{}; - delegate.template connect(); - calls->emplace_back(std::move(delegate)); + template + [[nodiscard]] auto constrained_find(const Other &value, std::size_t bucket) const { + for(auto it = cbegin(bucket), last = cend(bucket); it != last; ++it) { + if(packed.second()(*it, value)) { + return cbegin() + static_cast(it.index()); + } + } + + return cend(); } - /** - * @brief Connects a member function or a free function with payload 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 delegate. On the other side, the signal handler performs - * checks to avoid multiple connections for the same function.
- * 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 Member or free function 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) { - if constexpr(std::is_member_function_pointer_v) { - disconnect(value_or_instance); - } else { - disconnect(); + 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 std::make_pair(it, false); } - delegate delegate{}; - delegate.template connect(value_or_instance); - calls->emplace_back(std::move(delegate)); - } + packed.first().emplace_back(sparse.first()[index], std::forward(value)); + sparse.first()[index] = packed.first().size() - 1u; + rehash_if_required(); - /** - * @brief Disconnects a free function from a signal. - * @tparam Function A valid free function pointer. - */ - template - void disconnect() { - delegate delegate{}; + return std::make_pair(--end(), true); + } - if constexpr(std::is_invocable_r_v) { - delegate.template connect(); - } else { - decltype(payload_type(Function)) payload = nullptr; - delegate.template connect(payload); + void move_and_pop(const std::size_t pos) { + if(const auto last = size() - 1u; pos != last) { + packed.first()[pos] = std::move(packed.first().back()); + size_type *curr = sparse.first().data() + value_to_bucket(packed.first().back().second); + for(; *curr != last; curr = &packed.first()[*curr].first) {} + *curr = pos; } - calls->erase(std::remove(calls->begin(), calls->end(), std::move(delegate)), calls->end()); + packed.first().pop_back(); + } + + void rehash_if_required() { + if(size() > (bucket_count() * max_load_factor())) { + rehash(bucket_count() * 2u); + } } +public: + /*! @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 = std::size_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 Allocator type. */ + using allocator_type = Allocator; + /*! @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 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 Disconnects a given member function from a signal. - * @tparam Member Member function to disconnect from the signal. - * @tparam Class Type of class to which the member function belongs. - * @param instance A valid instance of type pointer to `Class`. + * @brief Constructs an empty container with a given allocator. + * @param allocator The allocator to use. */ - template - void disconnect(Class *instance) { - static_assert(std::is_member_function_pointer_v); - delegate delegate{}; - delegate.template connect(instance); - calls->erase(std::remove_if(calls->begin(), calls->end(), [&delegate](const auto &other) { - return other == delegate && other.instance() == delegate.instance(); - }), calls->end()); - } + explicit dense_set(const allocator_type &allocator) + : dense_set{minimum_capacity, hasher{}, key_equal{}, allocator} {} /** - * @brief Disconnects all the listeners from a signal. + * @brief Constructs an empty container with a given allocator and user + * supplied minimal number of buckets. + * @param bucket_count Minimal number of buckets. + * @param allocator The allocator to use. */ - void disconnect() { - calls->clear(); + dense_set(const size_type bucket_count, const allocator_type &allocator) + : dense_set{bucket_count, hasher{}, key_equal{}, allocator} {} + + /** + * @brief Constructs an empty container with a given allocator, hash + * function and user supplied minimal number of buckets. + * @param bucket_count Minimal number of buckets. + * @param hash Hash function to use. + * @param allocator The allocator to use. + */ + dense_set(const size_type bucket_count, const hasher &hash, const allocator_type &allocator) + : dense_set{bucket_count, 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 bucket_count 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 bucket_count, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{}) + : sparse{allocator, hash}, + packed{allocator, equal}, + threshold{default_threshold} { + rehash(bucket_count); } -private: - std::vector> *calls; -}; + /*! @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{std::piecewise_construct, std::forward_as_tuple(other.sparse.first(), allocator), std::forward_as_tuple(other.sparse.second())}, + packed{std::piecewise_construct, std::forward_as_tuple(other.packed.first(), allocator), std::forward_as_tuple(other.packed.second())}, + threshold{other.threshold} {} -/** - * @brief Unmanaged signal handler definition. - * - * Unmanaged signal handler. It works directly with naked pointers 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 used later to notify a bunch of listeners. - * * Collecting results from a set of functions like in a voting system. - * - * The default collector does nothing. To properly collect data, define and use - * a class that has a call operator the signature of which is `bool(Param)` and: - * - * * `Param` is a type to which `Ret` can be converted. - * * The return type is true if the handler must stop collecting data, false - * otherwise. - * - * @tparam Ret Return type of a function type. - * @tparam Args Types of arguments of a function type. - * @tparam Collector Type of collector to use, if any. - */ -template -struct sigh: private internal::invoker { - /*! @brief Unsigned integer type. */ - using size_type = typename std::vector>::size_type; - /*! @brief Collector type. */ - using collector_type = Collector; - /*! @brief Sink type. */ - using sink_type = entt::sink; + /*! @brief Default move constructor. */ + dense_set(dense_set &&) = default; /** - * @brief Instance type when it comes to connecting member functions. - * @tparam Class Type of class to which the member function belongs. + * @brief Allocator-extended move constructor. + * @param other The instance to move from. + * @param allocator The allocator to use. */ - template - using instance_type = Class *; + dense_set(dense_set &&other, const allocator_type &allocator) + : sparse{std::piecewise_construct, std::forward_as_tuple(std::move(other.sparse.first()), allocator), std::forward_as_tuple(std::move(other.sparse.second()))}, + packed{std::piecewise_construct, std::forward_as_tuple(std::move(other.packed.first()), allocator), std::forward_as_tuple(std::move(other.packed.second()))}, + threshold{other.threshold} {} /** - * @brief Number of listeners connected to the signal. - * @return Number of listeners currently connected. + * @brief Default copy assignment operator. + * @return This container. */ - size_type size() const ENTT_NOEXCEPT { - return calls.size(); - } + dense_set &operator=(const dense_set &) = default; /** - * @brief Returns false if at least a listener is connected to the signal. - * @return True if the signal has no listeners connected, false otherwise. + * @brief Default move assignment operator. + * @return This container. */ - bool empty() const ENTT_NOEXCEPT { - return calls.empty(); + dense_set &operator=(dense_set &&) = default; + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return sparse.first().get_allocator(); } /** - * @brief Returns a sink object for the given signal. + * @brief Returns an iterator to the beginning. * - * A sink is an opaque object used to connect listeners to signals.
- * The function type for a listener is the one of the signal to which it - * belongs. The order of invocation of the listeners isn't guaranteed. + * The returned iterator points to the first instance of the internal array. + * If the array is empty, the returned iterator will be equal to `end()`. * - * @return A temporary sink object. + * @return An iterator to the first instance of the internal array. */ - sink_type sink() ENTT_NOEXCEPT { - return { &calls }; + [[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT { + return packed.first().begin(); + } + + /*! @copydoc cbegin */ + [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT { + return cbegin(); + } + + /*! @copydoc begin */ + [[nodiscard]] iterator begin() ENTT_NOEXCEPT { + return packed.first().begin(); } /** - * @brief Triggers a signal. + * @brief Returns an iterator to the end. * - * All the listeners are notified. Order isn't guaranteed. + * The returned iterator points to the element following the last instance + * of the internal array. Attempting to dereference the returned iterator + * results in undefined behavior. * - * @param args Arguments to use to invoke listeners. + * @return An iterator to the element following the last instance of the + * internal array. */ - void publish(Args... args) const { - for(auto pos = calls.size(); pos; --pos) { - auto &call = calls[pos-1]; - call(args...); - } + [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { + return packed.first().end(); + } + + /*! @copydoc cend */ + [[nodiscard]] const_iterator end() const ENTT_NOEXCEPT { + return cend(); + } + + /*! @copydoc end */ + [[nodiscard]] iterator end() ENTT_NOEXCEPT { + return packed.first().end(); } /** - * @brief Collects return values from the listeners. - * @param args Arguments to use to invoke listeners. - * @return An instance of the collector filled with collected data. + * @brief Checks whether a container is empty. + * @return True if the container is empty, false otherwise. */ - collector_type collect(Args... args) const { - collector_type collector; + [[nodiscard]] bool empty() const ENTT_NOEXCEPT { + return packed.first().empty(); + } - for(auto &&call: calls) { - if(!this->invoke(collector, call, args...)) { - break; - } - } + /** + * @brief Returns the number of elements in a container. + * @return Number of elements in a container. + */ + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return packed.first().size(); + } - return collector; + /*! @brief Clears the container. */ + void clear() ENTT_NOEXCEPT { + sparse.first().clear(); + packed.first().clear(); + rehash(0u); } /** - * @brief Swaps listeners between the two signals. - * @param lhs A valid signal object. - * @param rhs A valid signal object. + * @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. */ - friend void swap(sigh &lhs, sigh &rhs) { - using std::swap; - swap(lhs.calls, rhs.calls); + std::pair insert(const value_type &value) { + return insert_or_do_nothing(value); } -private: - std::vector> calls; -}; + /*! @copydoc insert */ + std::pair insert(value_type &&value) { + return insert_or_do_nothing(std::move(value)); + } + /** + * @brief Inserts elements into the container, if they do not exist. + * @tparam It Type of input iterator. + * @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. + */ + template + void insert(It first, It 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 + std::pair emplace(Args &&...args) { + if constexpr(((sizeof...(Args) == 1u) && ... && std::is_same_v>, value_type>)) { + return insert_or_do_nothing(std::forward(args)...); + } else { + auto &node = packed.first().emplace_back(std::piecewise_construct, std::make_tuple(packed.first().size()), std::forward_as_tuple(std::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 std::make_pair(it, false); + } -#endif // ENTT_SIGNAL_SIGH_HPP + std::swap(node.first, sparse.first()[index]); + rehash_if_required(); -// #include "runtime_view.hpp" -#ifndef ENTT_ENTITY_RUNTIME_VIEW_HPP -#define ENTT_ENTITY_RUNTIME_VIEW_HPP + return std::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; + } -#include -#include -#include -#include -#include -// #include "../config/config.h" + /** + * @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(); -// #include "sparse_set.hpp" -#ifndef ENTT_ENTITY_SPARSE_SET_HPP -#define ENTT_ENTITY_SPARSE_SET_HPP + for(auto from = last - cbegin(); from != dist; --from) { + erase(packed.first()[from - 1u].second); + } + return (begin() + dist); + } -#include -#include -#include -#include -#include -#include -// #include "../config/config.h" + /** + * @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().data() + value_to_bucket(value); *curr != (std::numeric_limits::max)(); 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; + } + } -// #include "entity.hpp" -#ifndef ENTT_ENTITY_ENTITY_HPP -#define ENTT_ENTITY_ENTITY_HPP + return 0u; + } + /** + * @brief Exchanges the contents with those of a given container. + * @param other Container to exchange the content with. + */ + void swap(dense_set &other) { + using std::swap; + swap(sparse, other.sparse); + swap(packed, other.packed); + swap(threshold, other.threshold); + } -// #include "../config/config.h" + /** + * @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. + * @tparam Other Type of an element to search for. + * @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. + */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + find(const Other &value) { + return constrained_find(value, value_to_bucket(value)); + } -namespace entt { + /*! @copydoc find */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + find(const Other &value) const { + return constrained_find(value, value_to_bucket(value)); + } + /** + * @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 Entity traits. - * - * Primary template isn't defined on purpose. All the specializations give a - * compile-time error unless the template parameter is an accepted entity type. - */ -template -struct entt_traits; + /** + * @brief Checks if the container contains an element that compares + * _equivalent_ to a given value. + * @tparam Other Type of an element to search for. + * @param value Value of an element to search for. + * @return True if there is such an element, false otherwise. + */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + contains(const Other &value) const { + 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().begin(), sparse.first()[index]}; + } -/** - * @brief Entity traits for a 16 bits entity identifier. - * - * A 16 bits entity identifier guarantees: - * - * * 12 bits for the entity number (up to 4k entities). - * * 4 bit for the version (resets in [0-15]). - */ -template<> -struct entt_traits { - /*! @brief Underlying entity type. */ - using entity_type = std::uint16_t; - /*! @brief Underlying version type. */ - using version_type = std::uint8_t; - /*! @brief Difference type. */ - using difference_type = std::int32_t; + /** + * @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 Mask to use to get the entity number out of an identifier. */ - static constexpr std::uint16_t entity_mask = 0xFFF; - /*! @brief Mask to use to get the version out of an identifier. */ - static constexpr std::uint16_t version_mask = 0xF; - /*! @brief Extent of the entity number within an identifier. */ - static constexpr auto entity_shift = 12; -}; + /** + * @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().begin(), 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 {packed.first().begin(), (std::numeric_limits::max)()}; + } -/** - * @brief Entity traits for a 32 bits entity identifier. - * - * A 32 bits entity identifier guarantees: - * - * * 20 bits for the entity number (suitable for almost all the games). - * * 12 bit for the version (resets in [0-4095]). - */ -template<> -struct entt_traits { - /*! @brief Underlying entity type. */ - using entity_type = std::uint32_t; - /*! @brief Underlying version type. */ - using version_type = std::uint16_t; - /*! @brief Difference type. */ - using difference_type = std::int64_t; + /** + * @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([[maybe_unused]] const size_type index) const { + return cend(index); + } - /*! @brief Mask to use to get the entity number out of an identifier. */ - static constexpr std::uint32_t entity_mask = 0xFFFFF; - /*! @brief Mask to use to get the version out of an identifier. */ - static constexpr std::uint32_t version_mask = 0xFFF; - /*! @brief Extent of the entity number within an identifier. */ - static constexpr auto entity_shift = 20; -}; + /** + * @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 {packed.first().begin(), (std::numeric_limits::max)()}; + } + /** + * @brief Returns the number of buckets. + * @return The number of buckets. + */ + [[nodiscard]] size_type bucket_count() const { + return sparse.first().size(); + } -/** - * @brief Entity traits for a 64 bits entity identifier. - * - * A 64 bits entity identifier guarantees: - * - * * 32 bits for the entity number (an indecently large number). - * * 32 bit for the version (an indecently large number). - */ -template<> -struct entt_traits { - /*! @brief Underlying entity type. */ - using entity_type = std::uint64_t; - /*! @brief Underlying version type. */ - using version_type = std::uint32_t; - /*! @brief Difference type. */ - using difference_type = std::int64_t; + /** + * @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 Mask to use to get the entity number out of an identifier. */ - static constexpr std::uint64_t entity_mask = 0xFFFFFFFF; - /*! @brief Mask to use to get the version out of an identifier. */ - static constexpr std::uint64_t version_mask = 0xFFFFFFFF; - /*! @brief Extent of the entity number within an identifier. */ - static constexpr auto entity_shift = 32; -}; + /** + * @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(std::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); + } -/** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. - */ + /** + * @brief Returns the average number of elements per bucket. + * @return The average number of elements per bucket. + */ + [[nodiscard]] float load_factor() const { + return 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; + } -namespace internal { + /** + * @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 count New number of buckets. + */ + void rehash(const size_type count) { + auto value = (std::max)(count, minimum_capacity); + value = (std::max)(value, static_cast(size() / max_load_factor())); -struct null { - template - constexpr operator Entity() const ENTT_NOEXCEPT { - using traits_type = entt_traits; - return traits_type::entity_mask | (traits_type::version_mask << traits_type::entity_shift); - } + if(const auto sz = next_power_of_two(value); sz != bucket_count()) { + sparse.first().resize(sz); + std::fill(sparse.first().begin(), sparse.first().end(), (std::numeric_limits::max)()); - constexpr bool operator==(null) const ENTT_NOEXCEPT { - return true; + for(size_type pos{}, last = size(); pos < last; ++pos) { + const auto index = value_to_bucket(packed.first()[pos].second); + packed.first()[pos].first = std::exchange(sparse.first()[index], pos); + } + } } - constexpr bool operator!=(null) const ENTT_NOEXCEPT { - return false; + /** + * @brief Reserves space for at least the specified number of elements and + * regenerates the hash table. + * @param count New number of elements. + */ + void reserve(const size_type count) { + packed.first().reserve(count); + rehash(static_cast(std::ceil(count / max_load_factor()))); } - template - constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT { - return entity == static_cast(*this); + /** + * @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(); } - template - constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT { - return entity != static_cast(*this); + /** + * @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; }; +} // namespace entt -template -constexpr bool operator==(const Entity entity, null other) ENTT_NOEXCEPT { - return other == entity; -} +#endif +// #include "core/algorithm.hpp" +#ifndef ENTT_CORE_ALGORITHM_HPP +#define ENTT_CORE_ALGORITHM_HPP + +#include +#include +#include +#include +#include +// #include "utility.hpp" +#ifndef ENTT_CORE_UTILITY_HPP +#define ENTT_CORE_UTILITY_HPP + +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif -template -constexpr bool operator!=(const Entity entity, null other) ENTT_NOEXCEPT { - return other != entity; -} +namespace entt { + +/*! @brief Identity function object (waiting for C++20). */ +struct identity { + /*! @brief Indicates that this is a transparent function object. */ + using is_transparent = void; + + /** + * @brief Returns its argument unchanged. + * @tparam Type Type of the argument. + * @param value The actual argument. + * @return The submitted value as-is. + */ + template + [[nodiscard]] constexpr Type &&operator()(Type &&value) const ENTT_NOEXCEPT { + return std::forward(value); + } +}; +/** + * @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) ENTT_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) ENTT_NOEXCEPT { + return func; +} /** - * Internal details not to be documented. - * @endcond TURN_OFF_DOXYGEN + * @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 Null entity. - * - * There exist implicit conversions from this variable to entity identifiers of - * any allowed type. Similarly, there exist comparision operators between the - * null entity and any other entity identifier. + * @brief Basic implementation of a y-combinator. + * @tparam Func Type of a potentially recursive function. */ -constexpr auto null = internal::null{}; +template +struct y_combinator { + /** + * @brief Constructs a y-combinator from a given function. + * @param recursive A potentially recursive function. + */ + y_combinator(Func recursive) + : func{std::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 + decltype(auto) operator()(Args &&...args) const { + return func(*this, std::forward(args)...); + } -} + /*! @copydoc operator()() */ + template + decltype(auto) operator()(Args &&...args) { + return func(*this, std::forward(args)...); + } +private: + Func func; +}; -#endif // ENTT_ENTITY_ENTITY_HPP +} // namespace entt +#endif namespace entt { - /** - * @brief Basic 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 is largely used by the registry to offer users the fastest access ever - * to the components. Views and groups in general are almost entirely designed - * around sparse sets. - * - * 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 - * 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. - * - * @note - * Internal data structures arrange elements to maximize performance. Because of - * that, there are no guarantees that elements have the expected order when - * iterate directly the internal packed array (see `data` and `size` member - * functions for that). Use `begin` and `end` instead. + * @brief Function object to wrap `std::sort` in a class type. * - * @tparam Entity A valid entity type (see entt_traits for more details). + * Unfortunately, `std::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 `std::sort` in a + * function object. */ -template -class sparse_set { - using traits_type = entt_traits; +struct std_sort { + /** + * @brief Sorts the elements in a range. + * + * Sorts the elements in a range using the given binary comparison function. + * + * @tparam It Type of random access iterator. + * @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()(It first, It last, Compare compare = Compare{}, Args &&...args) const { + std::sort(std::forward(args)..., std::move(first), std::move(last), std::move(compare)); + } +}; - static_assert(ENTT_PAGE_SIZE && ((ENTT_PAGE_SIZE & (ENTT_PAGE_SIZE - 1)) == 0)); - static constexpr auto entt_per_page = ENTT_PAGE_SIZE / sizeof(typename entt_traits::entity_type); +/*! @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 It Type of random access iterator. + * @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()(It first, It last, Compare compare = Compare{}) const { + if(first < last) { + for(auto it = first + 1; it < last; ++it) { + auto value = std::move(*it); + auto pre = it; - class iterator { - friend class sparse_set; + for(; pre > first && compare(value, *(pre - 1)); --pre) { + *pre = std::move(*(pre - 1)); + } - using direct_type = const std::vector; - using index_type = typename traits_type::difference_type; + *pre = std::move(value); + } + } + } +}; - iterator(direct_type *ref, const index_type idx) ENTT_NOEXCEPT - : direct{ref}, index{idx} - {} +/** + * @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 +struct radix_sort { + static_assert((N % Bit) == 0, "The maximum number of bits to sort must be a multiple of the number of bits processed per pass"); - public: - using difference_type = index_type; - using value_type = Entity; - using pointer = const value_type *; - using reference = const value_type &; - using iterator_category = std::random_access_iterator_tag; + /** + * @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) { + static constexpr auto mask = (1 << Bit) - 1; + static constexpr auto buckets = 1 << Bit; + static constexpr auto passes = N / Bit; + + using value_type = typename std::iterator_traits::value_type; + std::vector aux(std::distance(first, last)); + + auto part = [getter = std::move(getter)](auto from, auto to, auto out, auto start) { + std::size_t index[buckets]{}; + std::size_t count[buckets]{}; + + for(auto it = from; it != to; ++it) { + ++count[(getter(*it) >> start) & mask]; + } - iterator() ENTT_NOEXCEPT = default; + for(std::size_t pos{}, end = buckets - 1u; pos < end; ++pos) { + index[pos + 1u] = index[pos] + count[pos]; + } - iterator & operator++() ENTT_NOEXCEPT { - return --index, *this; - } + for(auto it = from; it != to; ++it) { + out[index[(getter(*it) >> start) & mask]++] = std::move(*it); + } + }; - iterator operator++(int) ENTT_NOEXCEPT { - iterator orig = *this; - return ++(*this), orig; - } + for(std::size_t pass = 0; pass < (passes & ~1); pass += 2) { + part(first, last, aux.begin(), pass * Bit); + part(aux.begin(), aux.end(), first, (pass + 1) * Bit); + } - iterator & operator--() ENTT_NOEXCEPT { - return ++index, *this; + if constexpr(passes & 1) { + part(first, last, aux.begin(), (passes - 1) * Bit); + std::move(aux.begin(), aux.end(), first); + } } + } +}; - iterator operator--(int) ENTT_NOEXCEPT { - iterator orig = *this; - return --(*this), orig; - } +} // namespace entt - iterator & operator+=(const difference_type value) ENTT_NOEXCEPT { - index -= value; - return *this; - } +#endif - iterator operator+(const difference_type value) const ENTT_NOEXCEPT { - return iterator{direct, index-value}; - } +// #include "core/any.hpp" +#ifndef ENTT_CORE_ANY_HPP +#define ENTT_CORE_ANY_HPP - inline iterator & operator-=(const difference_type value) ENTT_NOEXCEPT { - return (*this += -value); - } +#include +#include +#include +#include +// #include "../config/config.h" - inline iterator operator-(const difference_type value) const ENTT_NOEXCEPT { - return (*this + -value); - } +// #include "../core/utility.hpp" +#ifndef ENTT_CORE_UTILITY_HPP +#define ENTT_CORE_UTILITY_HPP - difference_type operator-(const iterator &other) const ENTT_NOEXCEPT { - return other.index - index; - } +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H - reference operator[](const difference_type value) const ENTT_NOEXCEPT { - const auto pos = size_type(index-value-1); - return (*direct)[pos]; - } +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H - bool operator==(const iterator &other) const ENTT_NOEXCEPT { - return other.index == index; - } +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H - inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this == other); - } +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) - bool operator<(const iterator &other) const ENTT_NOEXCEPT { - return index > other.index; - } +#endif - bool operator>(const iterator &other) const ENTT_NOEXCEPT { - return index < other.index; - } - inline bool operator<=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this > other); - } +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 - inline bool operator>=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this < other); - } +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) - pointer operator->() const ENTT_NOEXCEPT { - const auto pos = size_type(index-1); - return &(*direct)[pos]; - } +#endif - inline reference operator*() const ENTT_NOEXCEPT { - return *operator->(); - } - private: - direct_type *direct; - index_type index; - }; +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 - void assure(const std::size_t page) { - if(!(page < reverse.size())) { - reverse.resize(page+1); - } +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif - if(!reverse[page].first) { - reverse[page].first = std::make_unique(entt_per_page); - // null is safe in all cases for our purposes - std::fill_n(reverse[page].first.get(), entt_per_page, null); - } - } +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif - auto index(const Entity entt) const ENTT_NOEXCEPT { - const auto identifier = entt & traits_type::entity_mask; - const auto page = size_type(identifier / entt_per_page); - const auto offset = size_type(identifier & (entt_per_page - 1)); - return std::make_pair(page, offset); - } -public: - /*! @brief Underlying entity identifier. */ - using entity_type = Entity; - /*! @brief Unsigned integer type. */ - using size_type = std::size_t; - /*! @brief Random access iterator type. */ - using iterator_type = iterator; +namespace entt { - /*! @brief Default constructor. */ - sparse_set() = default; +/*! @brief Identity function object (waiting for C++20). */ +struct identity { + /*! @brief Indicates that this is a transparent function object. */ + using is_transparent = void; /** - * @brief Copy constructor. - * @param other The instance to copy from. + * @brief Returns its argument unchanged. + * @tparam Type Type of the argument. + * @param value The actual argument. + * @return The submitted value as-is. */ - sparse_set(const sparse_set &other) - : reverse{}, - direct{other.direct} - { - for(size_type pos{}, last = other.reverse.size(); pos < last; ++pos) { - if(other.reverse[pos].first) { - assure(pos); - std::copy_n(other.reverse[pos].first.get(), entt_per_page, reverse[pos].first.get()); - reverse[pos].second = other.reverse[pos].second; - } - } + template + [[nodiscard]] constexpr Type &&operator()(Type &&value) const ENTT_NOEXCEPT { + return std::forward(value); } +}; - /*! @brief Default move constructor. */ - sparse_set(sparse_set &&) = default; - - /*! @brief Default destructor. */ - virtual ~sparse_set() ENTT_NOEXCEPT = default; +/** + * @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) ENTT_NOEXCEPT { + return member; +} - /** - * @brief Copy assignment operator. - * @param other The instance to copy from. - * @return This sparse set. - */ - sparse_set & operator=(const sparse_set &other) { - if(&other != this) { - auto tmp{other}; - *this = std::move(tmp); - } +/** + * @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) ENTT_NOEXCEPT { + return func; +} - return *this; - } +/** + * @brief Helper type for visitors. + * @tparam Func Types of function objects. + */ +template +struct overloaded: Func... { + using Func::operator()...; +}; - /*! @brief Default move assignment operator. @return This sparse set. */ - sparse_set & operator=(sparse_set &&) = default; +/** + * @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 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. + * @brief Constructs a y-combinator from a given function. + * @param recursive A potentially recursive function. */ - virtual void reserve(const size_type cap) { - direct.reserve(cap); - } + y_combinator(Func recursive) + : func{std::move(recursive)} {} /** - * @brief Returns the number of elements that a sparse set has currently - * allocated space for. - * @return Capacity of the sparse set. + * @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. */ - size_type capacity() const ENTT_NOEXCEPT { - return direct.capacity(); + template + decltype(auto) operator()(Args &&...args) const { + return func(*this, std::forward(args)...); } - /*! @brief Requests the removal of unused capacity. */ - virtual void shrink_to_fit() { - while(!reverse.empty() && !reverse.back().second) { - reverse.pop_back(); - } + /*! @copydoc operator()() */ + template + decltype(auto) operator()(Args &&...args) { + return func(*this, std::forward(args)...); + } - for(auto &&data: reverse) { - if(!data.second) { - data.first.reset(); - } +private: + Func func; +}; + +} // namespace entt + +#endif + +// #include "fwd.hpp" +#ifndef ENTT_CORE_FWD_HPP +#define ENTT_CORE_FWD_HPP + +#include +#include +// #include "../config/config.h" + + +namespace entt { + +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<>; + +} // namespace entt + +#endif + +// #include "type_info.hpp" +#ifndef ENTT_CORE_TYPE_INFO_HPP +#define ENTT_CORE_TYPE_INFO_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/attribute.h" +#ifndef ENTT_CORE_ATTRIBUTE_H +#define ENTT_CORE_ATTRIBUTE_H + +#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 + +#endif + +// #include "fwd.hpp" + +// #include "hashed_string.hpp" +#ifndef ENTT_CORE_HASHED_STRING_HPP +#define ENTT_CORE_HASHED_STRING_HPP + +#include +#include +// #include "../config/config.h" + +// #include "fwd.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct fnv1a_traits; + +template<> +struct fnv1a_traits { + using type = std::uint32_t; + static constexpr std::uint32_t offset = 2166136261; + static constexpr std::uint32_t prime = 16777619; +}; + +template<> +struct fnv1a_traits { + using type = std::uint64_t; + static constexpr std::uint64_t offset = 14695981039346656037ull; + static constexpr std::uint64_t prime = 1099511628211ull; +}; + +template +struct basic_hashed_string { + using value_type = Char; + using size_type = std::size_t; + using hash_type = id_type; + + const value_type *repr; + size_type length; + hash_type hash; +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @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 hs_traits = internal::fnv1a_traits; + + struct const_wrapper { + // non-explicit constructor on purpose + constexpr const_wrapper(const Char *str) ENTT_NOEXCEPT: repr{str} {} + const Char *repr; + }; + + // Fowler–Noll–Vo hash function v. 1a - the good + [[nodiscard]] static constexpr auto helper(const Char *str) ENTT_NOEXCEPT { + base_type base{str, 0u, hs_traits::offset}; + + for(; str[base.length]; ++base.length) { + base.hash = (base.hash ^ static_cast(str[base.length])) * hs_traits::prime; } - reverse.shrink_to_fit(); - direct.shrink_to_fit(); + return base; } - /** - * @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 the internal packed 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 Extent of the sparse set. - */ - size_type extent() const ENTT_NOEXCEPT { - return reverse.size() * entt_per_page; - } + // Fowler–Noll–Vo hash function v. 1a - the good + [[nodiscard]] static constexpr auto helper(const Char *str, const std::size_t len) ENTT_NOEXCEPT { + base_type base{str, len, hs_traits::offset}; - /** - * @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. - */ - size_type size() const ENTT_NOEXCEPT { - return direct.size(); - } + for(size_type pos{}; pos < len; ++pos) { + base.hash = (base.hash ^ static_cast(str[pos])) * hs_traits::prime; + } - /** - * @brief Checks whether a sparse set is empty. - * @return True if the sparse set is empty, false otherwise. - */ - bool empty() const ENTT_NOEXCEPT { - return direct.empty(); + return base; } +public: + /*! @brief Character type. */ + using value_type = typename base_type::value_type; + /*! @brief Unsigned integer type. */ + using size_type = typename base_type::size_type; + /*! @brief Unsigned integer type. */ + using hash_type = typename base_type::hash_type; + /** - * @brief Direct access to the internal packed array. - * - * The returned pointer is such that range `[data(), data() + size()]` is - * always a valid range, even if the container is empty. - * - * @note - * There are no guarantees on the order, even though `respect` has been - * previously invoked. Internal data structures arrange elements to maximize - * performance. Accessing them directly gives a performance boost but less - * guarantees. Use `begin` and `end` if you want to iterate the sparse set - * in the expected order. - * - * @return A pointer to the internal packed array. + * @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. */ - const entity_type * data() const ENTT_NOEXCEPT { - return direct.data(); + [[nodiscard]] static constexpr hash_type value(const value_type *str, const size_type len) ENTT_NOEXCEPT { + return basic_hashed_string{str, len}; } /** - * @brief Returns an iterator to the beginning. - * - * The returned iterator points to the first entity of the internal packed - * array. If the sparse set is empty, the returned iterator will be equal to - * `end()`. - * - * @note - * Input iterators stay true to the order imposed by a call to `respect`. - * - * @return An iterator to the first entity of the internal packed array. + * @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. */ - iterator_type begin() const ENTT_NOEXCEPT { - const typename traits_type::difference_type pos = direct.size(); - return iterator_type{&direct, pos}; + template + [[nodiscard]] static constexpr hash_type value(const value_type (&str)[N]) ENTT_NOEXCEPT { + return basic_hashed_string{str}; } /** - * @brief Returns an iterator to the end. - * - * The returned iterator points to the element following the last entity in - * the internal packed array. Attempting to dereference the returned - * iterator results in undefined behavior. - * - * @note - * Input iterators stay true to the order imposed by a call to `respect`. - * - * @return An iterator to the element following the last entity of the - * internal packed array. + * @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. */ - iterator_type end() const ENTT_NOEXCEPT { - return iterator_type{&direct, {}}; + [[nodiscard]] static constexpr hash_type value(const_wrapper wrapper) ENTT_NOEXCEPT { + return basic_hashed_string{wrapper}; } + /*! @brief Constructs an empty hashed string. */ + constexpr basic_hashed_string() ENTT_NOEXCEPT + : base_type{} {} + /** - * @brief Finds an entity. - * @param entt A valid entity identifier. - * @return An iterator to the given entity if it's found, past the end - * iterator otherwise. + * @brief Constructs a hashed string from a string view. + * @param str Human-readable identifier. + * @param len Length of the string to hash. */ - iterator_type find(const entity_type entt) const ENTT_NOEXCEPT { - return has(entt) ? --(end() - get(entt)) : end(); - } + constexpr basic_hashed_string(const value_type *str, const size_type len) ENTT_NOEXCEPT + : base_type{helper(str, len)} {} /** - * @brief Checks if a sparse set contains an entity. - * @param entt A valid entity identifier. - * @return True if the sparse set contains the entity, false otherwise. + * @brief Constructs a hashed string from an array of const characters. + * @tparam N Number of characters of the identifier. + * @param str Human-readable identifier. */ - bool has(const entity_type entt) const ENTT_NOEXCEPT { - auto [page, offset] = index(entt); - // testing against null permits to avoid accessing the direct vector - return (page < reverse.size() && reverse[page].second && reverse[page].first[offset] != null); - } + template + constexpr basic_hashed_string(const value_type (&str)[N]) ENTT_NOEXCEPT + : base_type{helper(str)} {} /** - * @brief Returns the position of an entity in a sparse set. + * @brief Explicit constructor on purpose to avoid constructing a hashed + * string directly from a `const value_type *`. * * @warning - * Attempting to get the position of an entity that doesn't belong to the - * sparse set results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * sparse set doesn't contain the given entity. + * The lifetime of the string is not extended nor is it copied. * - * @param entt A valid entity identifier. - * @return The position of the entity in the sparse set. + * @param wrapper Helps achieving the purpose by relying on overloading. */ - size_type get(const entity_type entt) const ENTT_NOEXCEPT { - ENTT_ASSERT(has(entt)); - auto [page, offset] = index(entt); - return size_type(reverse[page].first[offset]); - } + explicit constexpr basic_hashed_string(const_wrapper wrapper) ENTT_NOEXCEPT + : base_type{helper(wrapper.repr)} {} /** - * @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.
- * An assertion will abort the execution at runtime in debug mode if the - * sparse set already contains the given entity. - * - * @param entt A valid entity identifier. + * @brief Returns the size a hashed string. + * @return The size of the hashed string. */ - void construct(const entity_type entt) { - ENTT_ASSERT(!has(entt)); - auto [page, offset] = index(entt); - assure(page); - reverse[page].first[offset] = entity_type(direct.size()); - reverse[page].second++; - direct.push_back(entt); + [[nodiscard]] constexpr size_type size() const ENTT_NOEXCEPT { + return base_type::length; } /** - * @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.
- * An assertion will abort the execution at runtime in debug mode if the - * sparse set already contains the given entity. - * - * @tparam It Type of forward 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. + * @brief Returns the human-readable representation of a hashed string. + * @return The string used to initialize the hashed string. */ - template - void batch(It first, It last) { - std::for_each(first, last, [next = entity_type(direct.size()), this](const auto entt) mutable { - ENTT_ASSERT(!has(entt)); - auto [page, offset] = index(entt); - assure(page); - reverse[page].first[offset] = next++; - reverse[page].second++; - }); - - direct.insert(direct.end(), first, last); + [[nodiscard]] constexpr const value_type *data() const ENTT_NOEXCEPT { + return base_type::repr; } /** - * @brief Removes an entity from a sparse set. - * - * @warning - * Attempting to remove an entity that doesn't belong to the sparse set - * results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * sparse set doesn't contain the given entity. - * - * @param entt A valid entity identifier. + * @brief Returns the numeric representation of a hashed string. + * @return The numeric representation of the hashed string. */ - virtual void destroy(const entity_type entt) { - ENTT_ASSERT(has(entt)); - auto [from_page, from_offset] = index(entt); - auto [to_page, to_offset] = index(direct.back()); - direct[size_type(reverse[from_page].first[from_offset])] = direct.back(); - reverse[to_page].first[to_offset] = reverse[from_page].first[from_offset]; - reverse[from_page].first[from_offset] = null; - reverse[from_page].second--; - direct.pop_back(); + [[nodiscard]] constexpr hash_type value() const ENTT_NOEXCEPT { + return base_type::hash; } - /** - * @brief Swaps the position of two entities in the internal packed array. - * - * 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.
- * An assertion will abort the execution at runtime in debug mode if the - * sparse set doesn't contain the given entities. - * - * @param lhs A valid position within the sparse set. - * @param rhs A valid position within the sparse set. - */ - void swap(const size_type lhs, const size_type rhs) ENTT_NOEXCEPT { - ENTT_ASSERT(lhs < direct.size()); - ENTT_ASSERT(rhs < direct.size()); - auto [src_page, src_offset] = index(direct[lhs]); - auto [dst_page, dst_offset] = index(direct[rhs]); - std::swap(reverse[src_page].first[src_offset], reverse[dst_page].first[dst_offset]); - std::swap(direct[lhs], direct[rhs]); + /*! @copydoc data */ + [[nodiscard]] constexpr operator const value_type *() const ENTT_NOEXCEPT { + return data(); } /** - * @brief Sort entities according to their order in another sparse set. - * - * Entities that are part of both the sparse sets are ordered internally - * according to the order they have in `other`. All the other entities goes - * to the end of the list and there are no guarantess on their order.
- * In other terms, this function can be used to impose the same order on two - * sets by using one of them as a master and the other one as a slave. - * - * Iterating the sparse set with a couple of iterators returns elements in - * the expected order after a call to `respect`. See `begin` and `end` for - * more details. - * - * @note - * Attempting to iterate elements using the raw pointer returned by `data` - * gives no guarantees on the order, even though `respect` has been invoked. - * - * @param other The sparse sets that imposes the order of the entities. + * @brief Returns the numeric representation of a hashed string. + * @return The numeric representation of the hashed string. */ - virtual void respect(const sparse_set &other) ENTT_NOEXCEPT { - const auto to = other.end(); - auto from = other.begin(); - - size_type pos = direct.size() - 1; - - while(pos && from != to) { - if(has(*from)) { - if(*from != direct[pos]) { - swap(pos, get(*from)); - } - - --pos; - } - - ++from; - } + [[nodiscard]] constexpr operator hash_type() const ENTT_NOEXCEPT { + return value(); } - - /** - * @brief Resets a sparse set. - */ - virtual void reset() { - reverse.clear(); - direct.clear(); - } - -private: - std::vector, size_type>> reverse; - std::vector direct; }; +/** + * @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, const std::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 +basic_hashed_string(const Char (&str)[N]) -> basic_hashed_string; +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the two hashed strings are identical, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator==(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return lhs.value() == rhs.value(); } +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the two hashed strings differ, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator!=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} -#endif // ENTT_ENTITY_SPARSE_SET_HPP +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is less than the second, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator<(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return lhs.value() < rhs.value(); +} -// #include "entity.hpp" +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is less than or equal to the second, false + * otherwise. + */ +template +[[nodiscard]] constexpr bool operator<=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return !(rhs < lhs); +} -// #include "fwd.hpp" -#ifndef ENTT_ENTITY_FWD_HPP -#define ENTT_ENTITY_FWD_HPP +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is greater than the second, false + * otherwise. + */ +template +[[nodiscard]] constexpr bool operator>(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is greater than or equal to the second, + * false otherwise. + */ +template +[[nodiscard]] constexpr bool operator>=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} -#include -// #include "../config/config.h" +/*! @brief Aliases for common character types. */ +using hashed_string = basic_hashed_string; +/*! @brief Aliases for common character types. */ +using hashed_wstring = basic_hashed_string; +inline namespace literals { -namespace entt { +/** + * @brief User defined literal for hashed strings. + * @param str The literal without its suffix. + * @return A properly initialized hashed string. + */ +[[nodiscard]] constexpr hashed_string operator"" _hs(const char *str, std::size_t) ENTT_NOEXCEPT { + return hashed_string{str}; +} -/*! @class basic_registry */ -template -class basic_registry; +/** + * @brief User defined literal for hashed wstrings. + * @param str The literal without its suffix. + * @return A properly initialized hashed wstring. + */ +[[nodiscard]] constexpr hashed_wstring operator"" _hws(const wchar_t *str, std::size_t) ENTT_NOEXCEPT { + return hashed_wstring{str}; +} -/*! @class basic_view */ -template -class basic_view; +} // namespace literals -/*! @class basic_runtime_view */ -template -class basic_runtime_view; +} // namespace entt -/*! @class basic_group */ -template -class basic_group; +#endif -/*! @class basic_actor */ -template -struct basic_actor; -/*! @class basic_prototype */ -template -class basic_prototype; +namespace entt { -/*! @class basic_snapshot */ -template -class basic_snapshot; +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ -/*! @class basic_snapshot_loader */ -template -class basic_snapshot_loader; +namespace internal { -/*! @class basic_continuous_loader */ -template -class basic_continuous_loader; +struct ENTT_API type_index final { + [[nodiscard]] static id_type next() ENTT_NOEXCEPT { + static ENTT_MAYBE_ATOMIC(id_type) value{}; + return value++; + } +}; -/*! @brief Alias declaration for the most common use case. */ -using entity = std::uint32_t; +template +[[nodiscard]] constexpr auto stripped_type_name() ENTT_NOEXCEPT { +#if defined ENTT_PRETTY_FUNCTION + std::string_view pretty_function{ENTT_PRETTY_FUNCTION}; + auto first = pretty_function.find_first_not_of(' ', pretty_function.find_first_of(ENTT_PRETTY_FUNCTION_PREFIX) + 1); + auto value = pretty_function.substr(first, pretty_function.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first); + return value; +#else + return std::string_view{""}; +#endif +} -/*! @brief Alias declaration for the most common use case. */ -using registry = basic_registry; +template().find_first_of('.')> +[[nodiscard]] static constexpr std::string_view type_name(int) ENTT_NOEXCEPT { + constexpr auto value = stripped_type_name(); + return value; +} -/*! @brief Alias declaration for the most common use case. */ -using actor = basic_actor; +template +[[nodiscard]] static std::string_view type_name(char) ENTT_NOEXCEPT { + static const auto value = stripped_type_name(); + return value; +} -/*! @brief Alias declaration for the most common use case. */ -using prototype = basic_prototype; +template().find_first_of('.')> +[[nodiscard]] static constexpr id_type type_hash(int) ENTT_NOEXCEPT { + constexpr auto stripped = stripped_type_name(); + constexpr auto value = hashed_string::value(stripped.data(), stripped.size()); + return value; +} -/*! @brief Alias declaration for the most common use case. */ -using snapshot = basic_snapshot; +template +[[nodiscard]] static id_type type_hash(char) ENTT_NOEXCEPT { + static const auto value = [](const auto stripped) { + return hashed_string::value(stripped.data(), stripped.size()); + }(stripped_type_name()); + return value; +} -/*! @brief Alias declaration for the most common use case. */ -using snapshot_loader = basic_snapshot_loader; +} // namespace internal -/*! @brief Alias declaration for the most common use case. */ -using continuous_loader = basic_continuous_loader; +/** + * Internal details not to be documented. + * @endcond + */ /** - * @brief Alias declaration for the most common use case. - * @tparam Component Types of components iterated by the view. + * @brief Type sequential identifier. + * @tparam Type Type for which to generate a sequential identifier. */ -template -using view = basic_view; +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() ENTT_NOEXCEPT { + static const id_type value = internal::type_index::next(); + return value; + } -/*! @brief Alias declaration for the most common use case. */ -using runtime_view = basic_runtime_view; + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const ENTT_NOEXCEPT { + return value(); + } +}; /** - * @brief Alias declaration for the most common use case. - * @tparam Types Types of components iterated by the group. + * @brief Type hash. + * @tparam Type Type for which to generate a hash value. */ -template -using group = basic_group; +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() ENTT_NOEXCEPT { + return internal::type_hash(0); +#else + [[nodiscard]] static constexpr id_type value() ENTT_NOEXCEPT { + return type_index::value(); +#endif + } + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const ENTT_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 std::string_view value() ENTT_NOEXCEPT { + return internal::type_name(0); + } + /*! @copydoc value */ + [[nodiscard]] constexpr operator std::string_view() const ENTT_NOEXCEPT { + return value(); + } +}; -#endif // ENTT_ENTITY_FWD_HPP +/*! @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 + constexpr type_info(std::in_place_type_t) ENTT_NOEXCEPT + : seq{type_index>>::value()}, + identifier{type_hash>>::value()}, + alias{type_name>>::value()} {} + /** + * @brief Type index. + * @return Type index. + */ + [[nodiscard]] constexpr id_type index() const ENTT_NOEXCEPT { + return seq; + } + /** + * @brief Type hash. + * @return Type hash. + */ + [[nodiscard]] constexpr id_type hash() const ENTT_NOEXCEPT { + return identifier; + } -namespace entt { + /** + * @brief Type name. + * @return Type name. + */ + [[nodiscard]] constexpr std::string_view name() const ENTT_NOEXCEPT { + return alias; + } +private: + id_type seq; + id_type identifier; + std::string_view alias; +}; /** - * @brief Runtime view. - * - * Runtime views iterate over those entities that have at least all the given - * components in their bags. During initialization, a runtime view looks at the - * number of entities available for each component and picks up a reference to - * the smallest set of candidate entities in order to get a performance boost - * when iterate.
- * Order of elements during iterations are highly dependent on the order of the - * underlying data structures. See sparse_set and its specializations for more - * details. - * - * @b Important - * - * Iterators aren't invalidated if: - * - * * New instances of the given components are created and assigned to entities. - * * The entity currently pointed is modified (as an example, if one of the - * given components is removed from the entity to which the iterator points). - * - * In all the other cases, modifying the pools of the given components in any - * way invalidates all the iterators and using them results in undefined - * behavior. - * - * @note - * Views share references to the underlying data structures of the registry that - * generated them. Therefore any change to the entities and to the components - * made by means of the registry are immediately reflected by the views, unless - * a pool was missing when the view was built (in this case, the view won't - * have a valid reference and won't be updated accordingly). - * - * @warning - * Lifetime of a view must overcome the one of the registry that generated it. - * In any other case, attempting to use a view results in undefined behavior. - * - * @tparam Entity A valid entity type (see entt_traits for more details). + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects are identical, false otherwise. */ -template -class basic_runtime_view { - /*! @brief A registry is allowed to create views. */ - friend class basic_registry; +[[nodiscard]] inline constexpr bool operator==(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return lhs.hash() == rhs.hash(); +} - using underlying_iterator_type = typename sparse_set::iterator_type; - using extent_type = typename sparse_set::size_type; - using traits_type = entt_traits; +/** + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects differ, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator!=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} - class iterator { - friend class basic_runtime_view; +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than the second, false otherwise. + */ +[[nodiscard]] constexpr bool operator<(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return lhs.index() < rhs.index(); +} - iterator(underlying_iterator_type first, underlying_iterator_type last, const sparse_set * const *others, const sparse_set * const *length, extent_type ext) ENTT_NOEXCEPT - : begin{first}, - end{last}, - from{others}, - to{length}, - extent{ext} - { - if(begin != end && !valid()) { - ++(*this); - } - } +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than or equal to the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator<=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(rhs < lhs); +} - bool valid() const ENTT_NOEXCEPT { - const auto entt = *begin; - const auto sz = size_type(entt & traits_type::entity_mask); +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator>(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} - return sz < extent && std::all_of(from, to, [entt](const auto *view) { - return view->has(entt); - }); - } +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than or equal to the second, + * false otherwise. + */ +[[nodiscard]] constexpr bool operator>=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} - public: - using difference_type = typename underlying_iterator_type::difference_type; - using value_type = typename underlying_iterator_type::value_type; - using pointer = typename underlying_iterator_type::pointer; - using reference = typename underlying_iterator_type::reference; - using iterator_category = std::forward_iterator_tag; +/** + * @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() ENTT_NOEXCEPT { + if constexpr(std::is_same_v>>) { + static type_info instance{std::in_place_type}; + return instance; + } else { + return type_id>>(); + } +} - iterator() ENTT_NOEXCEPT = default; +/*! @copydoc type_id */ +template +[[nodiscard]] const type_info &type_id(Type &&) ENTT_NOEXCEPT { + return type_id>>(); +} - iterator & operator++() ENTT_NOEXCEPT { - return (++begin != end && !valid()) ? ++(*this) : *this; - } +} // namespace entt - iterator operator++(int) ENTT_NOEXCEPT { - iterator orig = *this; - return ++(*this), orig; - } +#endif - bool operator==(const iterator &other) const ENTT_NOEXCEPT { - return other.begin == begin; - } +// #include "type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP - inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this == other); - } +#include +#include +#include +#include +// #include "../config/config.h" - pointer operator->() const ENTT_NOEXCEPT { - return begin.operator->(); - } +// #include "fwd.hpp" - inline reference operator*() const ENTT_NOEXCEPT { - return *operator->(); - } - private: - underlying_iterator_type begin; - underlying_iterator_type end; - const sparse_set * const *from; - const sparse_set * const *to; - extent_type extent; - }; +namespace entt { - basic_runtime_view(std::vector *> others) ENTT_NOEXCEPT - : pools{std::move(others)} - { - const auto it = std::min_element(pools.begin(), pools.end(), [](const auto *lhs, const auto *rhs) { - return (!lhs && rhs) || (lhs && rhs && lhs->size() < rhs->size()); - }); +/** + * @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 TURN_OFF_DOXYGEN */ choice_t /*! @endcond */ +{}; - // brings the best candidate (if any) on front of the vector - std::rotate(pools.begin(), it, pools.end()); - } - - extent_type min() const ENTT_NOEXCEPT { - extent_type extent{}; - - if(valid()) { - const auto it = std::min_element(pools.cbegin(), pools.cend(), [](const auto *lhs, const auto *rhs) { - return lhs->extent() < rhs->extent(); - }); - - extent = (*it)->extent(); - } +/*! @copybrief choice_t */ +template<> +struct choice_t<0> {}; - return extent; - } +/** + * @brief Variable template for the choice trick. + * @tparam N Number of choices available. + */ +template +inline constexpr choice_t choice{}; - inline bool valid() const ENTT_NOEXCEPT { - return !pools.empty() && pools.front(); - } +/** + * @brief Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; -public: - /*! @brief Underlying entity identifier. */ - using entity_type = typename sparse_set::entity_type; - /*! @brief Unsigned integer type. */ - using size_type = typename sparse_set::size_type; - /*! @brief Input iterator type. */ - using iterator_type = iterator; +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; - /** - * @brief Estimates the number of entities that have the given components. - * @return Estimated number of entities that have the given components. - */ - size_type size() const ENTT_NOEXCEPT { - return valid() ? pools.front()->size() : size_type{}; - } +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; - /** - * @brief Checks if the view is definitely empty. - * @return True if the view is definitely empty, false otherwise. - */ - bool empty() const ENTT_NOEXCEPT { - return !valid() || pools.front()->empty(); - } +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; - /** - * @brief Returns an iterator to the first entity that has the given - * components. - * - * The returned iterator points to the first entity that has the given - * components. If the view is empty, the returned iterator will be equal to - * `end()`. - * - * @note - * Input iterators stay true to the order imposed to the underlying data - * structures. - * - * @return An iterator to the first entity that has the given components. - */ - iterator_type begin() const ENTT_NOEXCEPT { - iterator_type it{}; +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::size_t size_of_v = size_of::value; - if(valid()) { - const auto &pool = *pools.front(); - const auto * const *data = pools.data(); - it = { pool.begin(), pool.end(), data + 1, data + pools.size(), min() }; - } +/** + * @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; - return it; - } +/** + * @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 Returns an iterator that is past the last entity that has the - * given components. - * - * The returned iterator points to the entity following the last entity that - * has the given components. Attempting to dereference the returned iterator - * results in undefined behavior. - * - * @note - * Input iterators stay true to the order imposed to the underlying data - * structures. - * - * @return An iterator to the entity following the last entity that has the - * given components. - */ - iterator_type end() const ENTT_NOEXCEPT { - iterator_type it{}; +/** + * @brief Wraps a static constant. + * @tparam Value A static constant. + */ +template +using integral_constant = std::integral_constant; - if(valid()) { - const auto &pool = *pools.front(); - it = { pool.end(), pool.end(), nullptr, nullptr, min() }; - } +/** + * @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; - return it; - } +/** + * @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 Checks if a view contains an entity. - * @param entt A valid entity identifier. - * @return True if the view contains the given entity, false otherwise. - */ - bool contains(const entity_type entt) const ENTT_NOEXCEPT { - return valid() && std::all_of(pools.cbegin(), pools.cend(), [entt](const auto *view) { - return view->has(entt) && view->data()[view->get(entt)] == entt; - }); - } +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_element; - /** - * @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. To get the components, users can use the registry with - * which the view was built.
- * 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 { - std::for_each(begin(), end(), func); - } +/** + * @brief Provides compile-time indexed access to the types of a type list. + * @tparam Index Index of the type to return. + * @tparam Type First type provided by the type list. + * @tparam Other Other types provided by the type list. + */ +template +struct type_list_element> + : type_list_element> {}; -private: - std::vector *> pools; +/** + * @brief Provides compile-time indexed access to the types of a type list. + * @tparam Type 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 = Type; }; +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; +/** + * @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 +constexpr type_list operator+(type_list, type_list) { + return {}; } +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_cat; -#endif // ENTT_ENTITY_RUNTIME_VIEW_HPP - -// #include "sparse_set.hpp" - -// #include "snapshot.hpp" -#ifndef ENTT_ENTITY_SNAPSHOT_HPP -#define ENTT_ENTITY_SNAPSHOT_HPP +/*! @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 = typename type_list_cat, List...>::type; +}; -#include -#include -#include -#include -#include -#include -// #include "../config/config.h" +/** + * @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; +}; -// #include "entity.hpp" +/** + * @brief Helper type. + * @tparam List Type lists to concatenate. + */ +template +using type_list_cat_t = typename type_list_cat::type; -// #include "fwd.hpp" +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; +}; +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; -namespace entt { +/** + * @brief Helper type. + * @tparam Type A type list. + */ +template +using type_list_unique_t = typename type_list_unique::type; +/** + * @brief Provides the member constant `value` 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; /** - * @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 components of interest.
- * This type can be used in both cases if provided with a correctly configured - * output archive. - * - * @tparam Entity A valid entity type (see entt_traits for more details). + * @copybrief type_list_contains + * @tparam Type Types provided by the type list. + * @tparam Other Type to look for. */ -template -class basic_snapshot { - /*! @brief A registry is allowed to create snapshots. */ - friend class basic_registry; +template +struct type_list_contains, Other>: std::disjunction...> {}; - using follow_fn_type = Entity(const basic_registry &, const Entity); +/** + * @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; - basic_snapshot(const basic_registry *source, Entity init, follow_fn_type *fn) ENTT_NOEXCEPT - : reg{source}, - seed{init}, - follow{fn} - {} +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_diff; - template - void get(Archive &archive, std::size_t sz, It first, It last) const { - archive(static_cast(sz)); +/** + * @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>...>; +}; - while(first != last) { - const auto entt = *(first++); +/** + * @brief Helper type. + * @tparam List Type lists between which to compute the difference. + */ +template +using type_list_diff_t = typename type_list_diff::type; - if(reg->template has(entt)) { - if constexpr(std::is_empty_v) { - archive(entt); - } else { - archive(entt, reg->template get(entt)); - } - } - } - } +/** + * @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); +}; - template - void component(Archive &archive, It first, It last, std::index_sequence) const { - std::array size{}; - auto begin = first; +/*! @brief Primary template isn't defined on purpose. */ +template +struct value_list_element; - while(begin != last) { - const auto entt = *(begin++); - ((reg->template has(entt) ? ++size[Indexes] : size[Indexes]), ...); - } +/** + * @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> {}; - (get(archive, size[Indexes], first, last), ...); - } +/** + * @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 value. */ + static constexpr auto value = Value; +}; -public: - /*! @brief Default move constructor. */ - basic_snapshot(basic_snapshot &&) = default; +/** + * @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 Default move assignment operator. @return This snapshot. */ - basic_snapshot & operator=(basic_snapshot &&) = default; - - /** - * @brief Puts aside all the entities that are still in use. - * - * Entities are serialized along with their versions. Destroyed entities are - * not taken in consideration by this function. - * - * @tparam Archive Type of output archive. - * @param archive A valid reference to an output archive. - * @return An object of this type to continue creating the snapshot. - */ - template - const basic_snapshot & entities(Archive &archive) const { - archive(static_cast(reg->alive())); - reg->each([&archive](const auto entt) { archive(entt); }); - return *this; - } +/** + * @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 +constexpr value_list operator+(value_list, value_list) { + return {}; +} - /** - * @brief Puts aside destroyed entities. - * - * Entities are serialized along with their versions. Entities that are - * still in use are not taken in consideration by this function. - * - * @tparam Archive Type of output archive. - * @param archive A valid reference to an output archive. - * @return An object of this type to continue creating the snapshot. - */ - template - const basic_snapshot & destroyed(Archive &archive) const { - auto size = reg->size() - reg->alive(); - archive(static_cast(size)); +/*! @brief Primary template isn't defined on purpose. */ +template +struct value_list_cat; - if(size) { - auto curr = seed; - archive(curr); +/*! @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<>; +}; - for(--size; size; --size) { - curr = follow(*reg, curr); - archive(curr); - } - } +/** + * @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 = typename value_list_cat, List...>::type; +}; - return *this; - } +/** + * @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 Puts aside the given components. - * - * Each instance is serialized together with the entity to which it belongs. - * Entities are serialized along with their versions. - * - * @tparam Component Types of components to serialize. - * @tparam Archive Type of output archive. - * @param archive A valid reference to an output archive. - * @return An object of this type to continue creating the snapshot. - */ - template - const basic_snapshot & component(Archive &archive) const { - if constexpr(sizeof...(Component) == 1) { - const auto sz = reg->template size(); - const auto *entities = reg->template data(); +/** + * @brief Helper type. + * @tparam List Value lists to concatenate. + */ +template +using value_list_cat_t = typename value_list_cat::type; - archive(static_cast(sz)); +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::false_type {}; - for(std::remove_const_t pos{}; pos < sz; ++pos) { - const auto entt = entities[pos]; +/** + * @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>: std::is_invocable {}; - if constexpr(std::is_empty_v) { - archive(entt); - } else { - archive(entt, reg->template get(entt)); - } - }; - } else { - (component(archive), ...); - } +/** + * @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>: std::is_invocable {}; - return *this; - } +/** + * @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 Puts aside the given components for the entities in a range. - * - * Each instance is serialized together with the entity to which it belongs. - * Entities are serialized along with their versions. - * - * @tparam Component Types of components to serialize. - * @tparam Archive Type of output archive. - * @tparam It Type of input iterator. - * @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. - * @return An object of this type to continue creating the snapshot. - */ - template - const basic_snapshot & component(Archive &archive, It first, It last) const { - component(archive, first, last, std::make_index_sequence{}); - return *this; - } +/*! @brief Same as std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::false_type {}; -private: - const basic_registry *reg; - const Entity seed; - follow_fn_type *follow; -}; +/** + * @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>: std::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 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 Entity A valid entity type (see entt_traits for more details). + * @brief Provides the member constant `value` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. */ -template -class basic_snapshot_loader { - /*! @brief A registry is allowed to create snapshot loaders. */ - friend class basic_registry; +template +struct is_complete: std::false_type {}; - using force_fn_type = void(basic_registry &, const Entity, const bool); +/*! @copydoc is_complete */ +template +struct is_complete>: std::true_type {}; - basic_snapshot_loader(basic_registry *source, force_fn_type *fn) ENTT_NOEXCEPT - : reg{source}, - force{fn} - { - // to restore a snapshot as a whole requires a clean registry - ENTT_ASSERT(reg->empty()); - } +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_complete_v = is_complete::value; - template - void assure(Archive &archive, bool destroyed) const { - Entity length{}; - archive(length); +/** + * @brief Provides the member constant `value` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; - while(length--) { - Entity entt{}; - archive(entt); - force(*reg, entt, destroyed); - } - } +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ - template - void assign(Archive &archive, Args... args) const { - Entity length{}; - archive(length); +namespace internal { - while(length--) { - static constexpr auto destroyed = false; - Entity entt{}; +template +struct has_iterator_category: std::false_type {}; - if constexpr(std::is_empty_v) { - archive(entt); - force(*reg, entt, destroyed); - reg->template assign(args..., entt); - } else { - Type instance{}; - archive(entt, instance); - force(*reg, entt, destroyed); - reg->template assign(args..., entt, std::as_const(instance)); - } - } - } +template +struct has_iterator_category::iterator_category>>: std::true_type {}; -public: - /*! @brief Default move constructor. */ - basic_snapshot_loader(basic_snapshot_loader &&) = default; +} // namespace internal - /*! @brief Default move assignment operator. @return This loader. */ - basic_snapshot_loader & operator=(basic_snapshot_loader &&) = default; +/** + * Internal details not to be documented. + * @endcond + */ - /** - * @brief Restores entities that were in use during serialization. - * - * This function restores the entities that were in use during serialization - * and gives them the versions they originally had. - * - * @tparam Archive Type of input archive. - * @param archive A valid reference to an input archive. - * @return A valid loader to continue restoring data. - */ - template - const basic_snapshot_loader & entities(Archive &archive) const { - static constexpr auto destroyed = false; - assure(archive, destroyed); - return *this; - } +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : internal::has_iterator_category {}; - /** - * @brief Restores entities that were destroyed during serialization. - * - * This function restores the entities that were destroyed during - * serialization and gives them the versions they originally had. - * - * @tparam Archive Type of input archive. - * @param archive A valid reference to an input archive. - * @return A valid loader to continue restoring data. - */ - template - const basic_snapshot_loader & destroyed(Archive &archive) const { - static constexpr auto destroyed = true; - assure(archive, destroyed); - return *this; - } +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_iterator_v = is_iterator::value; - /** - * @brief Restores components and assigns them to the right entities. - * - * The template parameter list must be exactly the same used during - * serialization. In the event that the entity to which the component is - * assigned doesn't exist yet, the loader will take care to create it with - * the version it originally had. - * - * @tparam Component Types of components to restore. - * @tparam Archive Type of input archive. - * @param archive A valid reference to an input archive. - * @return A valid loader to continue restoring data. - */ - template - const basic_snapshot_loader & component(Archive &archive) const { - (assign(archive), ...); - return *this; - } +/** + * @brief Provides the member constant `value` 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 + : std::conjunction, std::negation>> {}; - /** - * @brief Destroys those entities that have no components. - * - * In case all the entities were serialized but only part of the components - * was saved, it could happen that some of the entities have no components - * once restored.
- * This functions helps to identify and destroy those entities. - * - * @return A valid loader to continue restoring data. - */ - const basic_snapshot_loader & orphans() const { - reg->orphans([this](const auto entt) { - reg->destroy(entt); - }); +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_ebco_eligible_v = is_ebco_eligible::value; - return *this; - } +/** + * @brief Provides the member constant `value` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; -private: - basic_registry *reg; - force_fn_type *force; -}; +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; /** - * @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 accomodate 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 applications with - * the requirement of transferring somehow parts of the representation side to - * side. - * - * @tparam Entity A valid entity type (see entt_traits for more details). + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. */ -template -class basic_continuous_loader { - using traits_type = entt_traits; +template +struct is_equality_comparable: std::false_type {}; - void destroy(Entity entt) { - const auto it = remloc.find(entt); +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ - if(it == remloc.cend()) { - const auto local = reg->create(); - remloc.emplace(entt, std::make_pair(local, true)); - reg->destroy(local); - } - } +namespace internal { - void restore(Entity entt) { - const auto it = remloc.find(entt); +template +struct has_tuple_size_value: std::false_type {}; - if(it == remloc.cend()) { - const auto local = reg->create(); - remloc.emplace(entt, std::make_pair(local, true)); - } else { - remloc[entt].first = reg->valid(remloc[entt].first) ? remloc[entt].first : reg->create(); - // set the dirty flag - remloc[entt].second = true; - } - } +template +struct has_tuple_size_value::value)>>: std::true_type {}; - template - void update(Other &instance, Member Type:: *member) { - if constexpr(!std::is_same_v) { - return; - } else if constexpr(std::is_same_v) { - instance.*member = map(instance.*member); - } else { - // maybe a container? let's try... - for(auto &entt: instance.*member) { - entt = map(entt); - } - } +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} + +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} + +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; } +} - template - void assure(Archive &archive, void(basic_continuous_loader:: *member)(Entity)) { - Entity length{}; - archive(length); +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); + } +} - while(length--) { - Entity entt{}; - archive(entt); - (this->*member)(entt); +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; + +/** + * @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 = std::remove_const_t; +}; + +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; + +/** + * @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 = typename constness_as::type; + +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); + + 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 = std::remove_pointer_t()))>; +}; + +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @brief A SBO friendly, type-safe container for single values of any type. + * @tparam Len Size of the storage reserved for the small buffer optimization. + * @tparam Align Optional alignment requirement. + */ +template +class basic_any { + enum class operation : std::uint8_t { + copy, + move, + transfer, + assign, + destroy, + compare, + get + }; + + enum class policy : std::uint8_t { + owner, + ref, + cref + }; + + using storage_type = std::aligned_storage_t; + using vtable_type = const void *(const operation, const basic_any &, const void *); + + template + static constexpr bool in_situ = Len && alignof(Type) <= alignof(storage_type) && sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v; + + template + static const void *basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const basic_any &value, [[maybe_unused]] const void *other) { + static_assert(!std::is_same_v && std::is_same_v>, Type>, "Invalid type"); + const Type *element = nullptr; + + if constexpr(in_situ) { + element = value.owner() ? reinterpret_cast(&value.storage) : static_cast(value.instance); + } else { + element = static_cast(value.instance); } - } - template - void reset() { - for(auto &&ref: remloc) { - const auto local = ref.second.first; + switch(op) { + case operation::copy: + if constexpr(std::is_copy_constructible_v) { + static_cast(const_cast(other))->initialize(*element); + } + break; + case operation::move: + if constexpr(in_situ) { + if(value.owner()) { + return new(&static_cast(const_cast(other))->storage) Type{std::move(*const_cast(element))}; + } + } - if(reg->valid(local)) { - reg->template reset(local); + return (static_cast(const_cast(other))->instance = std::exchange(const_cast(value).instance, nullptr)); + case operation::transfer: + if constexpr(std::is_move_assignable_v) { + *const_cast(element) = std::move(*static_cast(const_cast(other))); + return other; } + [[fallthrough]]; + case operation::assign: + if constexpr(std::is_copy_assignable_v) { + *const_cast(element) = *static_cast(other); + return other; + } + break; + case operation::destroy: + if constexpr(in_situ) { + element->~Type(); + } else if constexpr(std::is_array_v) { + delete[] element; + } else { + delete element; + } + break; + case operation::compare: + if constexpr(!std::is_function_v && !std::is_array_v && is_equality_comparable_v) { + return *static_cast(element) == *static_cast(other) ? other : nullptr; + } else { + return (element == other) ? other : nullptr; + } + case operation::get: + return element; } - } - template - void assign(Archive &archive, [[maybe_unused]] Member Type:: *... member) { - Entity length{}; - archive(length); - - while(length--) { - Entity entt{}; + return nullptr; + } - if constexpr(std::is_empty_v) { - archive(entt); - restore(entt); - reg->template assign_or_replace(map(entt)); + template + void initialize([[maybe_unused]] Args &&...args) { + if constexpr(!std::is_void_v) { + info = &type_id>>(); + vtable = basic_vtable>>; + + if constexpr(std::is_lvalue_reference_v) { + static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v && ...), "Invalid arguments"); + mode = std::is_const_v> ? policy::cref : policy::ref; + instance = (std::addressof(args), ...); + } else if constexpr(in_situ) { + if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v) { + new(&storage) Type{std::forward(args)...}; + } else { + new(&storage) Type(std::forward(args)...); + } } else { - Other instance{}; - archive(entt, instance); - (update(instance, member), ...); - restore(entt); - reg->template assign_or_replace(map(entt), std::as_const(instance)); + if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v) { + instance = new Type{std::forward(args)...}; + } else { + instance = new Type(std::forward(args)...); + } } } } + basic_any(const basic_any &other, const policy pol) ENTT_NOEXCEPT + : instance{other.data()}, + info{other.info}, + vtable{other.vtable}, + mode{pol} {} + public: - /*! @brief Underlying entity identifier. */ - using entity_type = Entity; + /*! @brief Size of the internal storage. */ + static constexpr auto length = Len; + /*! @brief Alignment requirement. */ + static constexpr auto alignment = Align; + + /*! @brief Default constructor. */ + constexpr basic_any() ENTT_NOEXCEPT + : instance{}, + info{&type_id()}, + vtable{}, + mode{policy::owner} {} /** - * @brief Constructs a loader that is bound to a given registry. - * @param source A valid reference to a registry. + * @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. */ - basic_continuous_loader(basic_registry &source) ENTT_NOEXCEPT - : reg{&source} - {} - - /*! @brief Default move constructor. */ - basic_continuous_loader(basic_continuous_loader &&) = default; - - /*! @brief Default move assignment operator. @return This loader. */ - basic_continuous_loader & operator=(basic_continuous_loader &&) = default; + template + explicit basic_any(std::in_place_type_t, Args &&...args) + : basic_any{} { + initialize(std::forward(args)...); + } /** - * @brief Restores entities that were in use during serialization. - * - * This function restores the entities that were in use during serialization - * and creates local counterparts for them if required. - * - * @tparam Archive Type of input archive. - * @param archive A valid reference to an input archive. - * @return A non-const reference to this loader. + * @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 - basic_continuous_loader & entities(Archive &archive) { - assure(archive, &basic_continuous_loader::restore); - return *this; + template, basic_any>>> + basic_any(Type &&value) + : basic_any{} { + initialize>(std::forward(value)); } /** - * @brief Restores entities that were destroyed during serialization. - * - * This function restores the entities that were destroyed during - * serialization and creates local counterparts for them if required. - * - * @tparam Archive Type of input archive. - * @param archive A valid reference to an input archive. - * @return A non-const reference to this loader. + * @brief Copy constructor. + * @param other The instance to copy from. */ - template - basic_continuous_loader & destroyed(Archive &archive) { - assure(archive, &basic_continuous_loader::destroy); - return *this; + basic_any(const basic_any &other) + : basic_any{} { + if(other.vtable) { + other.vtable(operation::copy, other, this); + } } /** - * @brief Restores components and assigns them to the right entities. - * - * The template parameter list must be exactly the same used during - * serialization. In the event that the entity to which the component is - * assigned doesn't exist yet, the loader will take care to create a local - * counterpart for it.
- * Members can be either data members of type entity_type or containers of - * entities. In both cases, the loader will visit them and update the - * entities by replacing each one with its local counterpart. - * - * @tparam Component Type of component to restore. - * @tparam Archive Type of input archive. - * @tparam Type Types of components to update with local counterparts. - * @tparam Member Types of members to update with their local counterparts. - * @param archive A valid reference to an input archive. - * @param member Members to update with their local counterparts. - * @return A non-const reference to this loader. + * @brief Move constructor. + * @param other The instance to move from. */ - template - basic_continuous_loader & component(Archive &archive, Member Type:: *... member) { - (reset(), ...); - (assign(archive, member...), ...); - return *this; + basic_any(basic_any &&other) ENTT_NOEXCEPT + : instance{}, + info{other.info}, + vtable{other.vtable}, + mode{other.mode} { + if(other.vtable) { + other.vtable(operation::move, other, this); + } + } + + /*! @brief Frees the internal storage, whatever it means. */ + ~basic_any() { + if(vtable && owner()) { + vtable(operation::destroy, *this, nullptr); + } } /** - * @brief Helps to purge entities that no longer have a conterpart. - * - * Users should invoke this member function after restoring each snapshot, - * unless they know exactly what they are doing. - * - * @return A non-const reference to this loader. + * @brief Copy assignment operator. + * @param other The instance to copy from. + * @return This any object. */ - basic_continuous_loader & shrink() { - auto it = remloc.begin(); + basic_any &operator=(const basic_any &other) { + reset(); - while(it != remloc.cend()) { - const auto local = it->second.first; - bool &dirty = it->second.second; + if(other.vtable) { + other.vtable(operation::copy, other, this); + } - if(dirty) { - dirty = false; - ++it; - } else { - if(reg->valid(local)) { - reg->destroy(local); - } + return *this; + } - it = remloc.erase(it); - } + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This any object. + */ + basic_any &operator=(basic_any &&other) ENTT_NOEXCEPT { + reset(); + + if(other.vtable) { + other.vtable(operation::move, other, this); + info = other.info; + vtable = other.vtable; + mode = other.mode; } return *this; } /** - * @brief Destroys those entities that have no components. - * - * In case all the entities were serialized but only part of the components - * was saved, it could happen that some of the entities have no components - * once restored.
- * This functions helps to identify and destroy those entities. - * - * @return A non-const reference to this loader. + * @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. */ - basic_continuous_loader & orphans() { - reg->orphans([this](const auto entt) { - reg->destroy(entt); - }); - + template + std::enable_if_t, basic_any>, basic_any &> + operator=(Type &&value) { + emplace>(std::forward(value)); return *this; } /** - * @brief Tests if a loader knows about a given entity. - * @param entt An entity identifier. - * @return True if `entity` is managed by the loader, false otherwise. + * @brief Returns the object type if any, `type_id()` otherwise. + * @return The object type if any, `type_id()` otherwise. */ - bool has(entity_type entt) const ENTT_NOEXCEPT { - return (remloc.find(entt) != remloc.cend()); + [[nodiscard]] const type_info &type() const ENTT_NOEXCEPT { + return *info; } /** - * @brief Returns the identifier to which an entity refers. - * @param entt An entity identifier. - * @return The local identifier if any, the null entity otherwise. + * @brief Returns an opaque pointer to the contained instance. + * @return An opaque pointer the contained instance, if any. */ - entity_type map(entity_type entt) const ENTT_NOEXCEPT { - const auto it = remloc.find(entt); - entity_type other = null; + [[nodiscard]] const void *data() const ENTT_NOEXCEPT { + return vtable ? vtable(operation::get, *this, nullptr) : nullptr; + } - if(it != remloc.cend()) { - other = it->second.first; - } + /** + * @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 ENTT_NOEXCEPT { + return *info == req ? data() : nullptr; + } - return other; + /** + * @brief Returns an opaque pointer to the contained instance. + * @return An opaque pointer the contained instance, if any. + */ + [[nodiscard]] void *data() ENTT_NOEXCEPT { + return (!vtable || mode == policy::cref) ? nullptr : const_cast(vtable(operation::get, *this, nullptr)); } -private: - std::unordered_map> remloc; - basic_registry *reg; -}; + /** + * @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) ENTT_NOEXCEPT { + return *info == req ? data() : nullptr; + } + /** + * @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) { + reset(); + initialize(std::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 any &other) { + if(vtable && mode != policy::cref && *info == *other.info) { + return (vtable(operation::assign, *this, other.data()) != nullptr); + } + return false; + } -#endif // ENTT_ENTITY_SNAPSHOT_HPP + /*! @copydoc assign */ + bool assign(any &&other) { + if(vtable && mode != policy::cref && *info == *other.info) { + if(auto *val = other.data(); val) { + return (vtable(operation::transfer, *this, val) != nullptr); + } else { + return (vtable(operation::assign, *this, std::as_const(other).data()) != nullptr); + } + } -// #include "storage.hpp" -#ifndef ENTT_ENTITY_STORAGE_HPP -#define ENTT_ENTITY_STORAGE_HPP + return false; + } + /*! @brief Destroys contained object */ + void reset() { + if(vtable && owner()) { + vtable(operation::destroy, *this, nullptr); + } -#include -#include -#include -#include -#include -#include -#include -// #include "../config/config.h" + info = &type_id(); + vtable = nullptr; + mode = policy::owner; + } -// #include "../core/algorithm.hpp" + /** + * @brief Returns false if a wrapper is empty, true otherwise. + * @return False if the wrapper is empty, true otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return vtable != nullptr; + } -// #include "sparse_set.hpp" + /** + * @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. + */ + bool operator==(const basic_any &other) const ENTT_NOEXCEPT { + if(vtable && *info == *other.info) { + return (vtable(operation::compare, *this, other.data()) != nullptr); + } -// #include "entity.hpp" + return (!vtable && !other.vtable); + } + /** + * @brief Aliasing constructor. + * @return A wrapper that shares a reference to an unmanaged object. + */ + [[nodiscard]] basic_any as_ref() ENTT_NOEXCEPT { + return basic_any{*this, (mode == policy::cref ? policy::cref : policy::ref)}; + } + /*! @copydoc as_ref */ + [[nodiscard]] basic_any as_ref() const ENTT_NOEXCEPT { + return basic_any{*this, policy::cref}; + } -namespace entt { + /** + * @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 ENTT_NOEXCEPT { + return (mode == policy::owner); + } +private: + union { + const void *instance; + storage_type storage; + }; + const type_info *info; + vtable_type *vtable; + policy mode; +}; /** - * @brief Basic storage implementation. - * - * This class is a refinement of a sparse set that associates an object to an - * entity. The main purpose of this class is to extend sparse sets to store - * components in a registry. It guarantees fast access both to the elements and - * to the entities. - * - * @note - * Entities and objects have the same order. It's guaranteed both in case of raw - * access (either to entities or objects) and when using input iterators. - * - * @note - * Internal data structures arrange elements to maximize performance. Because of - * that, there are no guarantees that elements have the expected order when - * iterate directly the internal packed array (see `raw` and `size` member - * functions for that). Use `begin` and `end` instead. - * - * @warning - * Empty types aren't explicitly instantiated. Temporary objects are returned in - * place of the instances of the components and raw access isn't available for - * them. - * - * @sa sparse_set - * - * @tparam Entity A valid entity type (see entt_traits for more details). - * @tparam Type Type of objects assigned to the entities. + * @brief Checks if two wrappers differ in their content. + * @tparam Len Size of the storage reserved for the small buffer optimization. + * @tparam Align Alignment requirement. + * @param lhs A wrapper, either empty or not. + * @param rhs A wrapper, either empty or not. + * @return True if the two wrappers differ in their content, false otherwise. */ -template> -class basic_storage: public sparse_set { - using underlying_type = sparse_set; - using traits_type = entt_traits; +template +[[nodiscard]] inline bool operator!=(const basic_any &lhs, const basic_any &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} - template - class iterator { - friend class basic_storage; +/** + * @brief Performs type-safe access to the contained object. + * @tparam Type Type to which conversion is required. + * @tparam Len Size of the storage reserved for the small buffer optimization. + * @tparam Align Alignment requirement. + * @param data Target any object. + * @return The element converted to the requested type. + */ +template +Type any_cast(const basic_any &data) ENTT_NOEXCEPT { + const auto *const instance = any_cast>(&data); + ENTT_ASSERT(instance, "Invalid instance"); + return static_cast(*instance); +} - using instance_type = std::conditional_t, std::vector>; - using index_type = typename traits_type::difference_type; +/*! @copydoc any_cast */ +template +Type any_cast(basic_any &data) ENTT_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); +} - iterator(instance_type *ref, const index_type idx) ENTT_NOEXCEPT - : instances{ref}, index{idx} - {} +/*! @copydoc any_cast */ +template +Type any_cast(basic_any &&data) ENTT_NOEXCEPT { + if constexpr(std::is_copy_constructible_v>>) { + if(auto *const instance = any_cast>(&data); instance) { + return static_cast(std::move(*instance)); + } else { + return any_cast(data); + } + } else { + auto *const instance = any_cast>(&data); + ENTT_ASSERT(instance, "Invalid instance"); + return static_cast(std::move(*instance)); + } +} - public: - using difference_type = index_type; - using value_type = Type; - using pointer = std::conditional_t; - using reference = std::conditional_t; - using iterator_category = std::random_access_iterator_tag; +/*! @copydoc any_cast */ +template +const Type *any_cast(const basic_any *data) ENTT_NOEXCEPT { + const auto &info = type_id>>(); + return static_cast(data->data(info)); +} - iterator() ENTT_NOEXCEPT = default; +/*! @copydoc any_cast */ +template +Type *any_cast(basic_any *data) ENTT_NOEXCEPT { + const auto &info = type_id>>(); + // last attempt to make wrappers for const references return their values + return static_cast(static_cast, Type> *>(data)->data(info)); +} - iterator & operator++() ENTT_NOEXCEPT { - return --index, *this; - } +/** + * @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 storage 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, std::size_t Align = basic_any::alignment, typename... Args> +basic_any make_any(Args &&...args) { + return basic_any{std::in_place_type, std::forward(args)...}; +} - iterator operator++(int) ENTT_NOEXCEPT { - iterator orig = *this; - return ++(*this), orig; - } +/** + * @brief Forwards its argument and avoids copies for lvalue references. + * @tparam Len Size of the storage 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, std::size_t Align = basic_any::alignment, typename Type> +basic_any forward_as_any(Type &&value) { + return basic_any{std::in_place_type, std::decay_t, Type>>, std::forward(value)}; +} - iterator & operator--() ENTT_NOEXCEPT { - return ++index, *this; - } +} // namespace entt + +#endif + +// #include "core/attribute.h" +#ifndef ENTT_CORE_ATTRIBUTE_H +#define ENTT_CORE_ATTRIBUTE_H + +#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 + +#endif + +// #include "core/compressed_pair.hpp" +#ifndef ENTT_CORE_COMPRESSED_PAIR_HPP +#define ENTT_CORE_COMPRESSED_PAIR_HPP - iterator operator--(int) ENTT_NOEXCEPT { - iterator orig = *this; - return --(*this), orig; - } +#include +#include +#include +#include +// #include "../config/config.h" - iterator & operator+=(const difference_type value) ENTT_NOEXCEPT { - index -= value; - return *this; - } +// #include "type_traits.hpp" - iterator operator+(const difference_type value) const ENTT_NOEXCEPT { - return iterator{instances, index-value}; - } - inline iterator & operator-=(const difference_type value) ENTT_NOEXCEPT { - return (*this += -value); - } +namespace entt { - inline iterator operator-(const difference_type value) const ENTT_NOEXCEPT { - return (*this + -value); - } +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ - difference_type operator-(const iterator &other) const ENTT_NOEXCEPT { - return other.index - index; - } +namespace internal { - reference operator[](const difference_type value) const ENTT_NOEXCEPT { - const auto pos = size_type(index-value-1); - return (*instances)[pos]; - } +template +struct compressed_pair_element { + using reference = Type &; + using const_reference = const Type &; - bool operator==(const iterator &other) const ENTT_NOEXCEPT { - return other.index == index; - } + template>> + compressed_pair_element() + : value{} {} - inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this == other); - } + template>, compressed_pair_element>>> + compressed_pair_element(Args &&args) + : value{std::forward(args)} {} - bool operator<(const iterator &other) const ENTT_NOEXCEPT { - return index > other.index; - } + template + compressed_pair_element(std::tuple args, std::index_sequence) + : value{std::forward(std::get(args))...} {} - bool operator>(const iterator &other) const ENTT_NOEXCEPT { - return index < other.index; - } + [[nodiscard]] reference get() ENTT_NOEXCEPT { + return value; + } - inline bool operator<=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this > other); - } + [[nodiscard]] const_reference get() const ENTT_NOEXCEPT { + return value; + } - inline bool operator>=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this < other); - } +private: + Type value; +}; - pointer operator->() const ENTT_NOEXCEPT { - const auto pos = size_type(index-1); - return &(*instances)[pos]; - } +template +struct compressed_pair_element>>: Type { + using reference = Type &; + using const_reference = const Type &; + using base_type = Type; - inline reference operator*() const ENTT_NOEXCEPT { - return *operator->(); - } + template>> + compressed_pair_element() + : base_type{} {} - private: - instance_type *instances; - index_type index; - }; + template>, compressed_pair_element>>> + compressed_pair_element(Args &&args) + : base_type{std::forward(args)} {} -public: - /*! @brief Type of the objects associated with the entities. */ - using object_type = Type; - /*! @brief Underlying entity identifier. */ - using entity_type = typename underlying_type::entity_type; - /*! @brief Unsigned integer type. */ - using size_type = typename underlying_type::size_type; - /*! @brief Random access iterator type. */ - using iterator_type = iterator; - /*! @brief Constant random access iterator type. */ - using const_iterator_type = iterator; + template + compressed_pair_element(std::tuple args, std::index_sequence) + : base_type{std::forward(std::get(args))...} {} - /** - * @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 { - underlying_type::reserve(cap); - instances.reserve(cap); + [[nodiscard]] reference get() ENTT_NOEXCEPT { + return *this; } - /*! @brief Requests the removal of unused capacity. */ - void shrink_to_fit() override { - underlying_type::shrink_to_fit(); - instances.shrink_to_fit(); + [[nodiscard]] const_reference get() const ENTT_NOEXCEPT { + return *this; } +}; - /** - * @brief Direct access to the array of objects. - * - * The returned pointer is such that range `[raw(), raw() + size()]` is - * always a valid range, even if the container is empty. - * - * @note - * There are no guarantees on the order, even though either `sort` or - * `respect` has been previously invoked. Internal data structures arrange - * elements to maximize performance. Accessing them directly gives a - * performance boost but less guarantees. Use `begin` and `end` if you want - * to iterate the storage in the expected order. - * - * @return A pointer to the array of objects. - */ - const object_type * raw() const ENTT_NOEXCEPT { - return instances.data(); - } +} // namespace internal - /*! @copydoc raw */ - object_type * raw() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).raw()); - } +/** + * Internal details not to be documented. + * @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 Returns an iterator to the beginning. - * - * The returned iterator points to the first instance of the given type. If - * the storage is empty, the returned iterator will be equal to `end()`. + * @brief Default constructor, conditionally enabled. * - * @note - * Input iterators stay true to the order imposed by a call to either `sort` - * or `respect`. + * This constructor is only available when the types that the pair stores + * are both at least default constructible. * - * @return An iterator to the first instance of the given type. + * @tparam Dummy Dummy template parameter used for internal purposes. */ - const_iterator_type cbegin() const ENTT_NOEXCEPT { - const typename traits_type::difference_type pos = underlying_type::size(); - return const_iterator_type{&instances, pos}; - } - - /*! @copydoc cbegin */ - inline const_iterator_type begin() const ENTT_NOEXCEPT { - return cbegin(); - } + template && std::is_default_constructible_v>> + constexpr compressed_pair() + : first_base{}, + second_base{} {} - /*! @copydoc begin */ - iterator_type begin() ENTT_NOEXCEPT { - const typename traits_type::difference_type pos = underlying_type::size(); - return iterator_type{&instances, pos}; - } + /** + * @brief Copy constructor. + * @param other The instance to copy from. + */ + constexpr compressed_pair(const compressed_pair &other) = default; /** - * @brief Returns an iterator to the end. - * - * The returned iterator points to the element following the last instance - * of the given type. Attempting to dereference the returned iterator - * results in undefined behavior. - * - * @note - * Input iterators stay true to the order imposed by a call to either `sort` - * or `respect`. - * - * @return An iterator to the element following the last instance of the - * given type. + * @brief Move constructor. + * @param other The instance to move from. */ - const_iterator_type cend() const ENTT_NOEXCEPT { - return const_iterator_type{&instances, {}}; - } + constexpr compressed_pair(compressed_pair &&other) = default; - /*! @copydoc cend */ - inline const_iterator_type end() const ENTT_NOEXCEPT { - return cend(); - } + /** + * @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) + : first_base{std::forward(arg)}, + second_base{std::forward(other)} {} - /*! @copydoc end */ - iterator_type end() ENTT_NOEXCEPT { - return iterator_type{&instances, {}}; - } + /** + * @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(std::piecewise_construct_t, std::tuple args, std::tuple other) + : first_base{std::move(args), std::index_sequence_for{}}, + second_base{std::move(other), std::index_sequence_for{}} {} /** - * @brief Returns the object associated with an entity. - * - * @warning - * Attempting to use an entity that doesn't belong to the storage results in - * undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * storage doesn't contain the given entity. - * - * @param entt A valid entity identifier. - * @return The object associated with the entity. + * @brief Copy assignment operator. + * @param other The instance to copy from. + * @return This compressed pair object. */ - const object_type & get(const entity_type entt) const ENTT_NOEXCEPT { - return instances[underlying_type::get(entt)]; - } + constexpr compressed_pair &operator=(const compressed_pair &other) = default; - /*! @copydoc get */ - inline object_type & get(const entity_type entt) ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).get(entt)); - } + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This compressed pair object. + */ + constexpr compressed_pair &operator=(compressed_pair &&other) = default; /** - * @brief Returns a pointer to the object associated with an entity, if any. - * @param entt A valid entity identifier. - * @return The object associated with the entity, if any. + * @brief Returns the first element that a pair stores. + * @return The first element that a pair stores. */ - const object_type * try_get(const entity_type entt) const ENTT_NOEXCEPT { - return underlying_type::has(entt) ? (instances.data() + underlying_type::get(entt)) : nullptr; + [[nodiscard]] first_type &first() ENTT_NOEXCEPT { + return static_cast(*this).get(); } - /*! @copydoc try_get */ - inline object_type * try_get(const entity_type entt) ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).try_get(entt)); + /*! @copydoc first */ + [[nodiscard]] const first_type &first() const ENTT_NOEXCEPT { + return static_cast(*this).get(); } /** - * @brief Assigns an entity to a storage and constructs its object. - * - * This version accept both types that can be constructed in place directly - * and types like aggregates that do not work well with a placement new as - * performed usually under the hood during an _emplace back_. - * - * @warning - * Attempting to use an entity that already belongs to the storage results - * in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * storage already contains the given entity. - * - * @tparam Args Types of arguments to use to construct the object. - * @param entt A valid entity identifier. - * @param args Parameters to use to construct an object for the entity. - * @return The object associated with the entity. + * @brief Returns the second element that a pair stores. + * @return The second element that a pair stores. */ - template - object_type & construct(const entity_type entt, Args &&... args) { - if constexpr(std::is_aggregate_v) { - instances.emplace_back(Type{std::forward(args)...}); - } else { - instances.emplace_back(std::forward(args)...); - } - - // entity goes after component in case constructor throws - underlying_type::construct(entt); - return instances.back(); + [[nodiscard]] second_type &second() ENTT_NOEXCEPT { + return static_cast(*this).get(); } - /** - * @brief Assigns one or more entities to a storage and constructs their - * objects. - * - * The object type must be at least default constructible. - * - * @warning - * Attempting to assign an entity that already belongs to the storage - * results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * storage already contains the given entity. - * - * @tparam It Type of forward 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 A pointer to the array of instances just created and sorted the - * same of the entities. - */ - template - object_type * batch(It first, It last) { - static_assert(std::is_default_constructible_v); - const auto skip = instances.size(); - instances.insert(instances.end(), last-first, {}); - // entity goes after component in case constructor throws - underlying_type::batch(first, last); - return instances.data() + skip; + /*! @copydoc second */ + [[nodiscard]] const second_type &second() const ENTT_NOEXCEPT { + return static_cast(*this).get(); } /** - * @brief Removes an entity from a storage and destroys its object. - * - * @warning - * Attempting to use an entity that doesn't belong to the storage results in - * undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * storage doesn't contain the given entity. - * - * @param entt A valid entity identifier. + * @brief Swaps two compressed pair objects. + * @param other The compressed pair to swap with. */ - void destroy(const entity_type entt) override { - std::swap(instances[underlying_type::get(entt)], instances.back()); - instances.pop_back(); - underlying_type::destroy(entt); + void swap(compressed_pair &other) { + using std::swap; + swap(first(), other.first()); + swap(second(), other.second()); } /** - * @brief Sort instances according to the given comparison function. - * - * Sort the elements so that iterating the storage with a couple of - * iterators returns them in the expected order. See `begin` and `end` for - * more details. - * - * 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(const Entity, const Entity); - * bool(const Type &, const Type &); - * @endcode - * - * Moreover, the comparison function object shall induce a - * _strict weak ordering_ on the values. - * - * The sort function oject 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. - * - * The comparison function object received by the sort function object - * hasn't necessarily the type of the one passed along with the other - * parameters to this member function. - * - * @note - * Attempting to iterate elements using a raw pointer returned by a call to - * either `data` or `raw` gives no guarantees on the order, even though - * `sort` has been invoked. - * - * @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. + * @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 - void sort(Compare compare, Sort algo = Sort{}, Args &&... args) { - std::vector copy(instances.size()); - std::iota(copy.begin(), copy.end(), 0); - - if constexpr(std::is_invocable_v) { - static_assert(!std::is_empty_v); - - algo(copy.rbegin(), copy.rend(), [this, compare = std::move(compare)](const auto lhs, const auto rhs) { - return compare(std::as_const(instances[lhs]), std::as_const(instances[rhs])); - }, std::forward(args)...); + template + decltype(auto) get() ENTT_NOEXCEPT { + if constexpr(Index == 0u) { + return first(); } else { - algo(copy.rbegin(), copy.rend(), [compare = std::move(compare), data = underlying_type::data()](const auto lhs, const auto rhs) { - return compare(data[lhs], data[rhs]); - }, std::forward(args)...); + static_assert(Index == 1u, "Index out of bounds"); + return second(); } + } - for(size_type pos{}, last = copy.size(); pos < last; ++pos) { - auto curr = pos; - auto next = copy[curr]; - - while(curr != next) { - const auto lhs = copy[curr]; - const auto rhs = copy[next]; - std::swap(instances[lhs], instances[rhs]); - underlying_type::swap(lhs, rhs); - copy[curr] = curr; - curr = next; - next = copy[curr]; - } + /*! @copydoc get */ + template + decltype(auto) get() const ENTT_NOEXCEPT { + if constexpr(Index == 0u) { + return first(); + } else { + static_assert(Index == 1u, "Index out of bounds"); + return second(); } } +}; - /** - * @brief Sort instances according to the order of the entities in another - * sparse set. - * - * Entities that are part of both the storage are ordered internally - * according to the order they have in `other`. All the other entities goes - * to the end of the list and there are no guarantess on their order. - * Instances are sorted according to the entities to which they belong.
- * In other terms, this function can be used to impose the same order on two - * sets by using one of them as a master and the other one as a slave. - * - * Iterating the storage with a couple of iterators returns elements in the - * expected order after a call to `respect`. See `begin` and `end` for more - * details. - * - * @note - * Attempting to iterate elements using a raw pointer returned by a call to - * either `data` or `raw` gives no guarantees on the order, even though - * `respect` has been invoked. - * - * @param other The sparse sets that imposes the order of the entities. - */ - void respect(const sparse_set &other) ENTT_NOEXCEPT override { - const auto to = other.end(); - auto from = other.begin(); - - size_type pos = underlying_type::size() - 1; - const auto *local = underlying_type::data(); - - while(pos && from != to) { - const auto curr = *from; +/** + * @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, std::decay_t>; - if(underlying_type::has(curr)) { - if(curr != *(local + pos)) { - auto candidate = underlying_type::get(curr); - std::swap(instances[pos], instances[candidate]); - underlying_type::swap(pos, candidate); - } +/** + * @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 +inline void swap(compressed_pair &lhs, compressed_pair &rhs) { + lhs.swap(rhs); +} - --pos; - } +} // namespace entt - ++from; - } - } +// disable structured binding support for clang 6, it messes when specializing tuple_size +#if !defined __clang_major__ || __clang_major__ > 6 +namespace std { - /*! @brief Resets a storage. */ - void reset() override { - underlying_type::reset(); - instances.clear(); - } +/** + * @brief `std::tuple_size` specialization for `compressed_pair`s. + * @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 +struct tuple_size>: integral_constant {}; -private: - std::vector instances; +/** + * @brief `std::tuple_element` specialization for `compressed_pair`s. + * @tparam Index The index of the type to return. + * @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 +struct tuple_element>: conditional { + static_assert(Index < 2u, "Index out of bounds"); }; +} // namespace std +#endif -/*! @copydoc basic_storage */ -template -class basic_storage>>: public sparse_set { - using underlying_type = sparse_set; - using traits_type = entt_traits; - - class iterator { - friend class basic_storage; +#endif - using index_type = typename traits_type::difference_type; +// #include "core/enum.hpp" +#ifndef ENTT_CORE_ENUM_HPP +#define ENTT_CORE_ENUM_HPP - iterator(const index_type idx) ENTT_NOEXCEPT - : index{idx} - {} +#include +// #include "../config/config.h" - public: - using difference_type = index_type; - using value_type = Type; - using pointer = const value_type *; - using reference = value_type; - using iterator_category = std::input_iterator_tag; - iterator() ENTT_NOEXCEPT = default; +namespace entt { - iterator & operator++() ENTT_NOEXCEPT { - return --index, *this; - } +/** + * @brief Enable bitmask support for enum classes. + * @tparam Type The enum type for which to enable bitmask support. + */ +template +struct enum_as_bitmask: std::false_type {}; - iterator operator++(int) ENTT_NOEXCEPT { - iterator orig = *this; - return ++(*this), orig; - } +/*! @copydoc enum_as_bitmask */ +template +struct enum_as_bitmask>: std::is_enum {}; - iterator & operator--() ENTT_NOEXCEPT { - return ++index, *this; - } +/** + * @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; - iterator operator--(int) ENTT_NOEXCEPT { - iterator orig = *this; - return --(*this), orig; - } +} // namespace entt - iterator & operator+=(const difference_type value) ENTT_NOEXCEPT { - index -= value; - return *this; - } +/** + * @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 std::enable_if_t, Type> +operator|(const Type lhs, const Type rhs) ENTT_NOEXCEPT { + return static_cast(static_cast>(lhs) | static_cast>(rhs)); +} - iterator operator+(const difference_type value) const ENTT_NOEXCEPT { - return iterator{index-value}; - } +/*! @copydoc operator| */ +template +[[nodiscard]] constexpr std::enable_if_t, Type> +operator&(const Type lhs, const Type rhs) ENTT_NOEXCEPT { + return static_cast(static_cast>(lhs) & static_cast>(rhs)); +} - inline iterator & operator-=(const difference_type value) ENTT_NOEXCEPT { - return (*this += -value); - } +/*! @copydoc operator| */ +template +[[nodiscard]] constexpr std::enable_if_t, Type> +operator^(const Type lhs, const Type rhs) ENTT_NOEXCEPT { + return static_cast(static_cast>(lhs) ^ static_cast>(rhs)); +} - inline iterator operator-(const difference_type value) const ENTT_NOEXCEPT { - return (*this + -value); - } +/** + * @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 std::enable_if_t, Type> +operator~(const Type value) ENTT_NOEXCEPT { + return static_cast(~static_cast>(value)); +} - difference_type operator-(const iterator &other) const ENTT_NOEXCEPT { - return other.index - index; - } +/*! @copydoc operator~ */ +template +[[nodiscard]] constexpr std::enable_if_t, bool> +operator!(const Type value) ENTT_NOEXCEPT { + return !static_cast>(value); +} - reference operator[](const difference_type) const ENTT_NOEXCEPT { - return {}; - } +/*! @copydoc operator| */ +template +constexpr std::enable_if_t, Type &> +operator|=(Type &lhs, const Type rhs) ENTT_NOEXCEPT { + return (lhs = (lhs | rhs)); +} - bool operator==(const iterator &other) const ENTT_NOEXCEPT { - return other.index == index; - } +/*! @copydoc operator| */ +template +constexpr std::enable_if_t, Type &> +operator&=(Type &lhs, const Type rhs) ENTT_NOEXCEPT { + return (lhs = (lhs & rhs)); +} - inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this == other); - } +/*! @copydoc operator| */ +template +constexpr std::enable_if_t, Type &> +operator^=(Type &lhs, const Type rhs) ENTT_NOEXCEPT { + return (lhs = (lhs ^ rhs)); +} - bool operator<(const iterator &other) const ENTT_NOEXCEPT { - return index > other.index; - } +#endif - bool operator>(const iterator &other) const ENTT_NOEXCEPT { - return index < other.index; - } +// #include "core/family.hpp" +#ifndef ENTT_CORE_FAMILY_HPP +#define ENTT_CORE_FAMILY_HPP - inline bool operator<=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this > other); - } +// #include "../config/config.h" - inline bool operator>=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this < other); - } +// #include "fwd.hpp" - pointer operator->() const ENTT_NOEXCEPT { - return nullptr; - } - inline reference operator*() const ENTT_NOEXCEPT { - return {}; - } +namespace entt { - private: - index_type index; - }; +/** + * @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 { + inline static ENTT_MAYBE_ATOMIC(id_type) identifier{}; public: - /*! @brief Type of the objects associated with the entities. */ - using object_type = Type; - /*! @brief Underlying entity identifier. */ - using entity_type = typename underlying_type::entity_type; /*! @brief Unsigned integer type. */ - using size_type = typename underlying_type::size_type; - /*! @brief Random access iterator type. */ - using iterator_type = iterator; - - /** - * @brief Returns an iterator to the beginning. - * - * The returned iterator points to the first instance of the given type. If - * the storage is empty, the returned iterator will be equal to `end()`. - * - * @note - * Input iterators stay true to the order imposed by a call to either `sort` - * or `respect`. - * - * @return An iterator to the first instance of the given type. - */ - iterator_type cbegin() const ENTT_NOEXCEPT { - const typename traits_type::difference_type pos = underlying_type::size(); - return iterator_type{pos}; - } + using family_type = id_type; - /*! @copydoc cbegin */ - inline iterator_type begin() const ENTT_NOEXCEPT { - return cbegin(); - } - - /** - * @brief Returns an iterator to the end. - * - * The returned iterator points to the element following the last instance - * of the given type. Attempting to dereference the returned iterator - * results in undefined behavior. - * - * @note - * Input iterators stay true to the order imposed by a call to either `sort` - * or `respect`. - * - * @return An iterator to the element following the last instance of the - * given type. - */ - iterator_type cend() const ENTT_NOEXCEPT { - return iterator_type{}; - } - - /*! @copydoc cend */ - inline iterator_type end() const ENTT_NOEXCEPT { - return cend(); - } - - /** - * @brief Returns the object associated with an entity. - * - * @note - * Empty types aren't explicitly instantiated. Therefore, this function - * always returns a temporary object. - * - * @warning - * Attempting to use an entity that doesn't belong to the storage results in - * undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * storage doesn't contain the given entity. - * - * @param entt A valid entity identifier. - * @return The object associated with the entity. - */ - object_type get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { - ENTT_ASSERT(underlying_type::has(entt)); - return {}; - } + /*! @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 family_type + inline static const family_type type = identifier++; }; -/*! @copydoc basic_storage */ -template -struct storage: basic_storage {}; - - -} - - -#endif // ENTT_ENTITY_STORAGE_HPP - -// #include "entity.hpp" +} // namespace entt -// #include "group.hpp" -#ifndef ENTT_ENTITY_GROUP_HPP -#define ENTT_ENTITY_GROUP_HPP +#endif +// #include "core/hashed_string.hpp" +#ifndef ENTT_CORE_HASHED_STRING_HPP +#define ENTT_CORE_HASHED_STRING_HPP -#include -#include -#include +#include +#include // #include "../config/config.h" -// #include "../core/type_traits.hpp" - -// #include "sparse_set.hpp" - -// #include "storage.hpp" - // #include "fwd.hpp" - namespace entt { - /** - * @brief Alias for lists of observed components. - * @tparam Type List of types. + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. */ -template -struct get_t: type_list {}; +namespace internal { -/** - * @brief Variable template for lists of observed components. - * @tparam Type List of types. - */ -template -constexpr get_t get{}; +template +struct fnv1a_traits; + +template<> +struct fnv1a_traits { + using type = std::uint32_t; + static constexpr std::uint32_t offset = 2166136261; + static constexpr std::uint32_t prime = 16777619; +}; + +template<> +struct fnv1a_traits { + using type = std::uint64_t; + static constexpr std::uint64_t offset = 14695981039346656037ull; + static constexpr std::uint64_t prime = 1099511628211ull; +}; + +template +struct basic_hashed_string { + using value_type = Char; + using size_type = std::size_t; + using hash_type = id_type; + + const value_type *repr; + size_type length; + hash_type hash; +}; +} // namespace internal /** - * @brief Group. - * - * Primary template isn't defined on purpose. All the specializations give a - * compile-time error, but for a few reasonable cases. + * Internal details not to be documented. + * @endcond */ -template -class basic_group; - /** - * @brief Non-owning group. - * - * A non-owning group returns all the entities and only the entities that have - * at least the given components. Moreover, it's guaranteed that the entity list - * is tightly packed in memory for fast iterations.
- * In general, non-owning groups don't stay true to the order of any set of - * components unless users explicitly sort them. - * - * @b Important - * - * Iterators aren't invalidated if: - * - * * New instances of the given components are created and assigned to entities. - * * The entity currently pointed is modified (as an example, if one of the - * given components is removed from the entity to which the iterator points). - * - * In all the other cases, modifying the pools of the given components in any - * way invalidates all the iterators and using them results in undefined - * behavior. + * @brief Zero overhead unique identifier. * - * @note - * Groups share references to the underlying data structures of the registry - * that generated them. Therefore any change to the entities and to the - * components made by means of the registry are immediately reflected by all the - * groups.
- * Moreover, sorting a non-owning group affects all the instance of the same - * group (it means that users don't have to call `sort` on each instance to sort - * all of them because they share the set of entities). + * 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 - * Lifetime of a group must overcome the one of the registry that generated it. - * In any other case, attempting to use a group results in undefined behavior. + * This class doesn't take ownership of user-supplied strings nor does it make a + * copy of them. * - * @tparam Entity A valid entity type (see entt_traits for more details). - * @tparam Get Types of components observed by the group. + * @tparam Char Character type. */ -template -class basic_group> { - static_assert(sizeof...(Get) > 0); +template +class basic_hashed_string: internal::basic_hashed_string { + using base_type = internal::basic_hashed_string; + using hs_traits = internal::fnv1a_traits; - /*! @brief A registry is allowed to create groups. */ - friend class basic_registry; + struct const_wrapper { + // non-explicit constructor on purpose + constexpr const_wrapper(const Char *str) ENTT_NOEXCEPT: repr{str} {} + const Char *repr; + }; - template - using pool_type = std::conditional_t, const storage>, storage>; - - // we could use pool_type *..., but vs complains about it and refuses to compile for unknown reasons (likely a bug) - basic_group(sparse_set *ref, storage> *... get) ENTT_NOEXCEPT - : handler{ref}, - pools{get...} - {} - - template - inline void traverse(Func func, type_list) const { - for(const auto entt: *handler) { - if constexpr(std::is_invocable_v({}))...>) { - func(std::get *>(pools)->get(entt)...); - } else { - func(entt, std::get *>(pools)->get(entt)...); - } - }; - } + // Fowler–Noll–Vo hash function v. 1a - the good + [[nodiscard]] static constexpr auto helper(const Char *str) ENTT_NOEXCEPT { + base_type base{str, 0u, hs_traits::offset}; -public: - /*! @brief Underlying entity identifier. */ - using entity_type = typename sparse_set::entity_type; - /*! @brief Unsigned integer type. */ - using size_type = typename sparse_set::size_type; - /*! @brief Input iterator type. */ - using iterator_type = typename sparse_set::iterator_type; + for(; str[base.length]; ++base.length) { + base.hash = (base.hash ^ static_cast(str[base.length])) * hs_traits::prime; + } - /** - * @brief Returns the number of existing components of the given type. - * @tparam Component Type of component of which to return the size. - * @return Number of existing components of the given type. - */ - template - size_type size() const ENTT_NOEXCEPT { - return std::get *>(pools)->size(); + return base; } - /** - * @brief Returns the number of entities that have the given components. - * @return Number of entities that have the given components. - */ - size_type size() const ENTT_NOEXCEPT { - return handler->size(); - } + // Fowler–Noll–Vo hash function v. 1a - the good + [[nodiscard]] static constexpr auto helper(const Char *str, const std::size_t len) ENTT_NOEXCEPT { + base_type base{str, len, hs_traits::offset}; - /** - * @brief Returns the number of elements that a group has currently - * allocated space for. - * @return Capacity of the group. - */ - size_type capacity() const ENTT_NOEXCEPT { - return handler->capacity(); - } + for(size_type pos{}; pos < len; ++pos) { + base.hash = (base.hash ^ static_cast(str[pos])) * hs_traits::prime; + } - /*! @brief Requests the removal of unused capacity. */ - void shrink_to_fit() { - handler->shrink_to_fit(); + return base; } - /** - * @brief Checks whether the pool of a given component is empty. - * @tparam Component Type of component in which one is interested. - * @return True if the pool of the given component is empty, false - * otherwise. - */ - template - bool empty() const ENTT_NOEXCEPT { - return std::get *>(pools)->empty(); - } +public: + /*! @brief Character type. */ + using value_type = typename base_type::value_type; + /*! @brief Unsigned integer type. */ + using size_type = typename base_type::size_type; + /*! @brief Unsigned integer type. */ + using hash_type = typename base_type::hash_type; /** - * @brief Checks whether the group is empty. - * @return True if the group is empty, false otherwise. + * @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. */ - bool empty() const ENTT_NOEXCEPT { - return handler->empty(); + [[nodiscard]] static constexpr hash_type value(const value_type *str, const size_type len) ENTT_NOEXCEPT { + return basic_hashed_string{str, len}; } /** - * @brief Direct access to the list of components of a given pool. - * - * The returned pointer is such that range - * `[raw(), raw() + size()]` is always a - * valid range, even if the container is empty. - * - * @note - * There are no guarantees on the order of the components. Use `begin` and - * `end` if you want to iterate the group in the expected order. - * - * @tparam Component Type of component in which one is interested. - * @return A pointer to the array of components. + * @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 - Component * raw() const ENTT_NOEXCEPT { - return std::get *>(pools)->raw(); + template + [[nodiscard]] static constexpr hash_type value(const value_type (&str)[N]) ENTT_NOEXCEPT { + return basic_hashed_string{str}; } /** - * @brief Direct access to the list of entities of a given pool. - * - * The returned pointer is such that range - * `[data(), data() + size()]` is always a - * valid range, even if the container is empty. - * - * @note - * There are no guarantees on the order of the entities. Use `begin` and - * `end` if you want to iterate the group in the expected order. - * - * @tparam Component Type of component in which one is interested. - * @return A pointer to the array of entities. + * @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. */ - template - const entity_type * data() const ENTT_NOEXCEPT { - return std::get *>(pools)->data(); + [[nodiscard]] static constexpr hash_type value(const_wrapper wrapper) ENTT_NOEXCEPT { + return basic_hashed_string{wrapper}; } + /*! @brief Constructs an empty hashed string. */ + constexpr basic_hashed_string() ENTT_NOEXCEPT + : base_type{} {} + /** - * @brief Direct access to the list of entities. - * - * The returned pointer is such that range `[data(), data() + size()]` is - * always a valid range, even if the container is empty. - * - * @note - * There are no guarantees on the order of the entities. Use `begin` and - * `end` if you want to iterate the group in the expected order. - * - * @return A pointer to the array of entities. + * @brief Constructs a hashed string from a string view. + * @param str Human-readable identifier. + * @param len Length of the string to hash. */ - const entity_type * data() const ENTT_NOEXCEPT { - return handler->data(); - } + constexpr basic_hashed_string(const value_type *str, const size_type len) ENTT_NOEXCEPT + : base_type{helper(str, len)} {} /** - * @brief Returns an iterator to the first entity that has the given - * components. - * - * The returned iterator points to the first entity that has the given - * components. If the group is empty, the returned iterator will be equal to - * `end()`. - * - * @note - * Input iterators stay true to the order imposed to the underlying data - * structures. - * - * @return An iterator to the first entity that has the given components. + * @brief Constructs a hashed string from an array of const characters. + * @tparam N Number of characters of the identifier. + * @param str Human-readable identifier. */ - iterator_type begin() const ENTT_NOEXCEPT { - return handler->begin(); - } + template + constexpr basic_hashed_string(const value_type (&str)[N]) ENTT_NOEXCEPT + : base_type{helper(str)} {} /** - * @brief Returns an iterator that is past the last entity that has the - * given components. - * - * The returned iterator points to the entity following the last entity that - * has the given components. Attempting to dereference the returned iterator - * results in undefined behavior. + * @brief Explicit constructor on purpose to avoid constructing a hashed + * string directly from a `const value_type *`. * - * @note - * Input iterators stay true to the order imposed to the underlying data - * structures. + * @warning + * The lifetime of the string is not extended nor is it copied. * - * @return An iterator to the entity following the last entity that has the - * given components. - */ - iterator_type end() const ENTT_NOEXCEPT { - return handler->end(); - } - - /** - * @brief Finds an entity. - * @param entt A valid entity identifier. - * @return An iterator to the given entity if it's found, past the end - * iterator otherwise. - */ - iterator_type find(const entity_type entt) const ENTT_NOEXCEPT { - const auto it = handler->find(entt); - return it != end() && *it == entt ? it : end(); - } - - /** - * @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. + * @param wrapper Helps achieving the purpose by relying on overloading. */ - entity_type operator[](const size_type pos) const ENTT_NOEXCEPT { - return begin()[pos]; - } + explicit constexpr basic_hashed_string(const_wrapper wrapper) ENTT_NOEXCEPT + : base_type{helper(wrapper.repr)} {} /** - * @brief Checks if a group contains an entity. - * @param entt A valid entity identifier. - * @return True if the group contains the given entity, false otherwise. + * @brief Returns the size a hashed string. + * @return The size of the hashed string. */ - bool contains(const entity_type entt) const ENTT_NOEXCEPT { - return find(entt) != end(); + [[nodiscard]] constexpr size_type size() const ENTT_NOEXCEPT { + return base_type::length; } /** - * @brief Returns the components assigned to the given entity. - * - * Prefer this function instead of `registry::get` during iterations. It has - * far better performance than its companion function. - * - * @warning - * Attempting to use an invalid component type results in a compilation - * error. Attempting to use an entity that doesn't belong to the group - * results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * group doesn't contain the given entity. - * - * @tparam Component Types of components to get. - * @param entt A valid entity identifier. - * @return The components assigned to the entity. + * @brief Returns the human-readable representation of a hashed string. + * @return The string used to initialize the hashed string. */ - template - decltype(auto) get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { - ENTT_ASSERT(contains(entt)); - - if constexpr(sizeof...(Component) == 1) { - return (std::get *>(pools)->get(entt), ...); - } else { - return std::tuple(entt))...>{get(entt)...}; - } + [[nodiscard]] constexpr const value_type *data() const ENTT_NOEXCEPT { + return base_type::repr; } /** - * @brief Iterates entities and components 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 all its components. The - * _constness_ of the components is as requested.
- * The signature of the function must be equivalent to one of the following - * forms: - * - * @code{.cpp} - * void(const entity_type, Get &...); - * void(Get &...); - * @endcode - * - * @note - * Empty types aren't explicitly instantiated. Therefore, temporary objects - * are returned during iterations. They can be caught only by copy or with - * const references. - * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. + * @brief Returns the numeric representation of a hashed string. + * @return The numeric representation of the hashed string. */ - template - inline void each(Func func) const { - traverse(std::move(func), type_list{}); + [[nodiscard]] constexpr hash_type value() const ENTT_NOEXCEPT { + return base_type::hash; } - /** - * @brief Iterates entities and components 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 components. The - * _constness_ of the components 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 - * - * @sa each - * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. - */ - template - inline void less(Func func) const { - using non_empty_get = type_list_cat_t, type_list<>, type_list>...>; - traverse(std::move(func), non_empty_get{}); + /*! @copydoc data */ + [[nodiscard]] constexpr operator const value_type *() const ENTT_NOEXCEPT { + return data(); } /** - * @brief Sort the shared pool of entities according to the given component. - * - * Non-owning groups of the same type share with the registry a pool of - * entities with its own order that doesn't depend on the order of any pool - * of components. Users can order the underlying data structure so that it - * respects the order of the pool of the given component. - * - * @note - * The shared pool of entities and thus its order is affected by the changes - * to each and every pool that it tracks. Therefore changes to those pools - * can quickly ruin the order imposed to the pool of entities shared between - * the non-owning groups. - * - * @tparam Component Type of component to use to impose the order. + * @brief Returns the numeric representation of a hashed string. + * @return The numeric representation of the hashed string. */ - template - void sort() const { - handler->respect(*std::get *>(pools)); + [[nodiscard]] constexpr operator hash_type() const ENTT_NOEXCEPT { + return value(); } - -private: - sparse_set *handler; - const std::tuple *...> pools; }; - /** - * @brief Owning group. - * - * Owning groups return all the entities and only the entities that have at - * least the given components. Moreover: - * - * * It's guaranteed that the entity list is tightly packed in memory for fast - * iterations. - * * It's guaranteed that the lists of owned components are tightly packed in - * memory for even faster iterations and to allow direct access. - * * They stay true to the order of the owned components and all the owned - * components have the same order in memory. - * - * The more types of components are owned by a group, the faster it is to - * iterate them. - * - * @b Important - * - * Iterators aren't invalidated if: - * - * * New instances of the given components are created and assigned to entities. - * * The entity currently pointed is modified (as an example, if one of the - * given components is removed from the entity to which the iterator points). - * - * In all the other cases, modifying the pools of the given components in any - * way invalidates all the iterators and using them results in undefined - * behavior. - * - * @note - * Groups share references to the underlying data structures of the registry - * that generated them. Therefore any change to the entities and to the - * components made by means of the registry are immediately reflected by all the - * groups. - * Moreover, sorting an owning group affects all the instance of the same group - * (it means that users don't have to call `sort` on each instance to sort all - * of them because they share the underlying data structure). - * - * @warning - * Lifetime of a group must overcome the one of the registry that generated it. - * In any other case, attempting to use a group results in undefined behavior. - * - * @tparam Entity A valid entity type (see entt_traits for more details). - * @tparam Get Types of components observed by the group. - * @tparam Owned Types of components owned by the group. + * @brief Deduction guide. + * @tparam Char Character type. + * @param str Human-readable identifier. + * @param len Length of the string to hash. */ -template -class basic_group, Owned...> { - static_assert(sizeof...(Get) + sizeof...(Owned) > 0); - - /*! @brief A registry is allowed to create groups. */ - friend class basic_registry; +template +basic_hashed_string(const Char *str, const std::size_t len) -> basic_hashed_string; - template - using pool_type = std::conditional_t, const storage>, storage>; +/** + * @brief Deduction guide. + * @tparam Char Character type. + * @tparam N Number of characters of the identifier. + * @param str Human-readable identifier. + */ +template +basic_hashed_string(const Char (&str)[N]) -> basic_hashed_string; - template - using component_iterator_type = decltype(std::declval>().begin()); +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the two hashed strings are identical, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator==(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return lhs.value() == rhs.value(); +} - // we could use pool_type *..., but vs complains about it and refuses to compile for unknown reasons (likely a bug) - basic_group(const typename basic_registry::size_type *sz, storage> *... owned, storage> *... get) ENTT_NOEXCEPT - : length{sz}, - pools{owned..., get...} - {} +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the two hashed strings differ, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator!=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} - template - decltype(auto) from_index(const typename sparse_set::size_type index) { - static_assert(!std::is_empty_v); +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is less than the second, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator<(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return lhs.value() < rhs.value(); +} - if constexpr(std::disjunction_v...>) { - return std::as_const(*std::get *>(pools)).raw()[index]; - } else { - return std::as_const(*std::get *>(pools)).get(data()[index]); - } - } +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is less than or equal to the second, false + * otherwise. + */ +template +[[nodiscard]] constexpr bool operator<=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return !(rhs < lhs); +} - template - inline auto swap(int, const std::size_t lhs, const std::size_t rhs) - -> decltype(std::declval>().raw(), void()) { - auto *cpool = std::get *>(pools); - std::swap(cpool->raw()[lhs], cpool->raw()[rhs]); - cpool->swap(lhs, rhs); - } +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is greater than the second, false + * otherwise. + */ +template +[[nodiscard]] constexpr bool operator>(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} - template - inline void swap(char, const std::size_t lhs, const std::size_t rhs) { - std::get *>(pools)->swap(lhs, rhs); - } +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is greater than or equal to the second, + * false otherwise. + */ +template +[[nodiscard]] constexpr bool operator>=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} - template - inline void traverse(Func func, type_list, type_list) const { - auto raw = std::make_tuple((std::get *>(pools)->end() - *length)...); - [[maybe_unused]] auto data = std::get<0>(pools)->sparse_set::end() - *length; +/*! @brief Aliases for common character types. */ +using hashed_string = basic_hashed_string; - for(auto next = *length; next; --next) { - if constexpr(std::is_invocable_v({}))..., decltype(get({}))...>) { - if constexpr(sizeof...(Weak) == 0) { - func(*(std::get>(raw)++)...); - } else { - const auto entt = *(data++); - func(*(std::get>(raw)++)..., std::get *>(pools)->get(entt)...); - } - } else { - const auto entt = *(data++); - func(entt, *(std::get>(raw)++)..., std::get *>(pools)->get(entt)...); - } - } +/*! @brief Aliases for common character types. */ +using hashed_wstring = 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]] constexpr hashed_string operator"" _hs(const char *str, std::size_t) ENTT_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]] constexpr hashed_wstring operator"" _hws(const wchar_t *str, std::size_t) ENTT_NOEXCEPT { + return hashed_wstring{str}; +} + +} // namespace literals + +} // namespace entt + +#endif + +// #include "core/ident.hpp" +#ifndef ENTT_CORE_IDENT_HPP +#define ENTT_CORE_IDENT_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #include "fwd.hpp" + +// #include "type_traits.hpp" + + +namespace entt { + +/** + * @brief Types identifiers. + * + * Variable template used to generate identifiers at compile-time for the given + * types. Use the `get` member function to know what's the identifier associated + * to the specific type. + * + * @note + * Identifiers are constant expression and can be used in any context where such + * an expression is required. As an example: + * @code{.cpp} + * using id = entt::identifier; + * + * switch(a_type_identifier) { + * case id::type: + * // ... + * break; + * case id::type: + * // ... + * break; + * default: + * // ... + * } + * @endcode + * + * @tparam Types List of types for which to generate identifiers. + */ +template +class identifier { + template + [[nodiscard]] static constexpr id_type get(std::index_sequence) ENTT_NOEXCEPT { + static_assert((std::is_same_v || ...), "Invalid type"); + return (0 + ... + (std::is_same_v...>>> ? id_type{Index} : id_type{})); } public: - /*! @brief Underlying entity identifier. */ - using entity_type = typename sparse_set::entity_type; /*! @brief Unsigned integer type. */ - using size_type = typename sparse_set::size_type; - /*! @brief Input iterator type. */ - using iterator_type = typename sparse_set::iterator_type; + using identifier_type = id_type; + + /*! @brief Statically generated unique identifier for the given type. */ + template + static constexpr identifier_type type = get>(std::index_sequence_for{}); +}; + +} // namespace entt + +#endif + +// #include "core/iterator.hpp" +#ifndef ENTT_CORE_ITERATOR_HPP +#define ENTT_CORE_ITERATOR_HPP + +#include +#include +#include +// #include "../config/config.h" + + +namespace entt { + +/** + * @brief Helper type to use as pointer with input iterators. + * @tparam Type of wrapped value. + */ +template +struct input_iterator_pointer final { + /*! @brief Pointer type. */ + using pointer = Type *; + + /*! @brief Default copy constructor, deleted on purpose. */ + input_iterator_pointer(const input_iterator_pointer &) = delete; + + /*! @brief Default move constructor. */ + input_iterator_pointer(input_iterator_pointer &&) = default; /** - * @brief Returns the number of existing components of the given type. - * @tparam Component Type of component of which to return the size. - * @return Number of existing components of the given type. + * @brief Constructs a proxy object by move. + * @param val Value to use to initialize the proxy object. */ - template - size_type size() const ENTT_NOEXCEPT { - return std::get *>(pools)->size(); - } + input_iterator_pointer(Type &&val) + : value{std::move(val)} {} /** - * @brief Returns the number of entities that have the given components. - * @return Number of entities that have the given components. + * @brief Default copy assignment operator, deleted on purpose. + * @return This proxy object. */ - size_type size() const ENTT_NOEXCEPT { - return *length; - } + input_iterator_pointer &operator=(const input_iterator_pointer &) = delete; /** - * @brief Checks whether the pool of a given component is empty. - * @tparam Component Type of component in which one is interested. - * @return True if the pool of the given component is empty, false - * otherwise. + * @brief Default move assignment operator. + * @return This proxy object. */ - template - bool empty() const ENTT_NOEXCEPT { - return std::get *>(pools)->empty(); - } + input_iterator_pointer &operator=(input_iterator_pointer &&) = default; /** - * @brief Checks whether the group is empty. - * @return True if the group is empty, false otherwise. + * @brief Access operator for accessing wrapped values. + * @return A pointer to the wrapped value. */ - bool empty() const ENTT_NOEXCEPT { - return !*length; + [[nodiscard]] pointer operator->() ENTT_NOEXCEPT { + return std::addressof(value); } +private: + Type value; +}; + +/** + * @brief Utility class to create an iterable object from a pair of iterators. + * @tparam It Type of iterator. + * @tparam Sentinel Type of sentinel. + */ +template +struct iterable_adaptor final { + /*! @brief Value type. */ + using value_type = typename std::iterator_traits::value_type; + /*! @brief Iterator type. */ + using iterator = It; + /*! @brief Sentinel type. */ + using sentinel = Sentinel; + + /*! @brief Default constructor. */ + iterable_adaptor() = default; + /** - * @brief Direct access to the list of components of a given pool. - * - * The returned pointer is such that range - * `[raw(), raw() + size()]` is always a - * valid range, even if the container is empty.
- * Moreover, in case the group owns the given component, the range - * `[raw(), raw() + size()]` is such that it contains - * the instances that are part of the group itself. - * - * @note - * There are no guarantees on the order of the components. Use `begin` and - * `end` if you want to iterate the group in the expected order. - * - * @tparam Component Type of component in which one is interested. - * @return A pointer to the array of components. + * @brief Creates an iterable object from a pair of iterators. + * @param from Begin iterator. + * @param to End iterator. */ - template - Component * raw() const ENTT_NOEXCEPT { - return std::get *>(pools)->raw(); - } + iterable_adaptor(iterator from, sentinel to) + : first{from}, + last{to} {} /** - * @brief Direct access to the list of entities of a given pool. - * - * The returned pointer is such that range - * `[data(), data() + size()]` is always a - * valid range, even if the container is empty.
- * Moreover, in case the group owns the given component, the range - * `[data(), data() + size()]` is such that it - * contains the entities that are part of the group itself. - * - * @note - * There are no guarantees on the order of the entities. Use `begin` and - * `end` if you want to iterate the group in the expected order. - * - * @tparam Component Type of component in which one is interested. - * @return A pointer to the array of entities. + * @brief Returns an iterator to the beginning. + * @return An iterator to the first element of the range. */ - template - const entity_type * data() const ENTT_NOEXCEPT { - return std::get *>(pools)->data(); + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return first; } /** - * @brief Direct access to the list of entities. - * - * The returned pointer is such that range `[data(), data() + size()]` is - * always a valid range, even if the container is empty. - * - * @note - * There are no guarantees on the order of the entities. Use `begin` and - * `end` if you want to iterate the group in the expected order. - * - * @return A pointer to the array of entities. + * @brief Returns an iterator to the end. + * @return An iterator to the element following the last element of the + * range. */ - const entity_type * data() const ENTT_NOEXCEPT { - return std::get<0>(pools)->data(); + [[nodiscard]] sentinel end() const ENTT_NOEXCEPT { + return last; } - /** - * @brief Returns an iterator to the first entity that has the given - * components. - * - * The returned iterator points to the first entity that has the given - * components. If the group is empty, the returned iterator will be equal to - * `end()`. - * - * @note - * Input iterators stay true to the order imposed to the underlying data - * structures. - * - * @return An iterator to the first entity that has the given components. - */ - iterator_type begin() const ENTT_NOEXCEPT { - return std::get<0>(pools)->sparse_set::end() - *length; + /*! @copydoc begin */ + [[nodiscard]] iterator cbegin() const ENTT_NOEXCEPT { + return begin(); } - /** - * @brief Returns an iterator that is past the last entity that has the - * given components. - * - * The returned iterator points to the entity following the last entity that - * has the given components. Attempting to dereference the returned iterator - * results in undefined behavior. - * - * @note - * Input iterators stay true to the order imposed to the underlying data - * structures. - * - * @return An iterator to the entity following the last entity that has the - * given components. - */ - iterator_type end() const ENTT_NOEXCEPT { - return std::get<0>(pools)->sparse_set::end(); + /*! @copydoc end */ + [[nodiscard]] sentinel cend() const ENTT_NOEXCEPT { + return end(); } - /** - * @brief Finds an entity. - * @param entt A valid entity identifier. - * @return An iterator to the given entity if it's found, past the end - * iterator otherwise. - */ - iterator_type find(const entity_type entt) const ENTT_NOEXCEPT { - const auto it = std::get<0>(pools)->find(entt); - return it != end() && it >= begin() && *it == entt ? it : end(); +private: + It first; + Sentinel last; +}; + +} // namespace entt + +#endif + +// #include "core/memory.hpp" +#ifndef ENTT_CORE_MEMORY_HPP +#define ENTT_CORE_MEMORY_HPP + +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" + + +namespace entt { + +/** + * @brief Unwraps fancy pointers, does nothing otherwise (waiting for C++20). + * @tparam Type Pointer type. + * @param ptr Fancy or raw pointer. + * @return A raw pointer that represents the address of the original pointer. + */ +template +[[nodiscard]] constexpr auto to_address(Type &&ptr) ENTT_NOEXCEPT { + if constexpr(std::is_pointer_v>>) { + return ptr; + } else { + return to_address(std::forward(ptr).operator->()); } +} - /** - * @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. - */ - entity_type operator[](const size_type pos) const ENTT_NOEXCEPT { - return begin()[pos]; +/** + * @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) ENTT_NOEXCEPT { + if constexpr(std::allocator_traits::propagate_on_container_copy_assignment::value) { + lhs = rhs; } +} - /** - * @brief Checks if a group contains an entity. - * @param entt A valid entity identifier. - * @return True if the group contains the given entity, false otherwise. - */ - bool contains(const entity_type entt) const ENTT_NOEXCEPT { - return find(entt) != end(); +/** + * @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) ENTT_NOEXCEPT { + if constexpr(std::allocator_traits::propagate_on_container_move_assignment::value) { + lhs = std::move(rhs); } +} - /** - * @brief Returns the components assigned to the given entity. - * - * Prefer this function instead of `registry::get` during iterations. It has - * far better performance than its companion function. - * - * @warning - * Attempting to use an invalid component type results in a compilation - * error. Attempting to use an entity that doesn't belong to the group - * results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * group doesn't contain the given entity. - * - * @tparam Component Types of components to get. - * @param entt A valid entity identifier. - * @return The components assigned to the entity. - */ - template - decltype(auto) get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { - ENTT_ASSERT(contains(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_swap([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) ENTT_NOEXCEPT { + ENTT_ASSERT(std::allocator_traits::propagate_on_container_swap::value || lhs == rhs, "Cannot swap the containers"); - if constexpr(sizeof...(Component) == 1) { - return (std::get *>(pools)->get(entt), ...); - } else { - return std::tuple(entt))...>{get(entt)...}; - } + if constexpr(std::allocator_traits::propagate_on_container_swap::value) { + using std::swap; + swap(lhs, rhs); } +} - /** - * @brief Iterates entities and components 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 all its components. The - * _constness_ of the components is as requested.
- * The signature of the function must be equivalent to one of the following - * forms: - * - * @code{.cpp} - * void(const entity_type, Owned &..., Get &...); - * void(Owned &..., Get &...); - * @endcode - * - * @note - * Empty types aren't explicitly instantiated. Therefore, temporary objects - * are returned during iterations. They can be caught only by copy or with - * const references. - * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. - */ - template - inline void each(Func func) const { - traverse(std::move(func), type_list{}, type_list{}); +/** + * @brief Checks whether a value is a power of two or not. + * @param value A value that may or may not be a power of two. + * @return True if the value is a power of two, false otherwise. + */ +[[nodiscard]] inline constexpr bool is_power_of_two(const std::size_t value) ENTT_NOEXCEPT { + return value && ((value & (value - 1)) == 0); +} + +/** + * @brief Computes the smallest power of two greater than or equal to a value. + * @param value The value to use. + * @return The smallest power of two greater than or equal to the given value. + */ +[[nodiscard]] inline constexpr std::size_t next_power_of_two(const std::size_t value) ENTT_NOEXCEPT { + ENTT_ASSERT(value < (std::size_t{1u} << (std::numeric_limits::digits - 1)), "Numeric limits exceeded"); + std::size_t curr = value - (value != 0u); + + for(int next = 1; next < std::numeric_limits::digits; next = next * 2) { + curr |= curr >> next; } + return ++curr; +} + +/** + * @brief Fast module utility function (powers of two only). + * @param value A value for which to calculate the modulus. + * @param mod _Modulus_, it must be a power of two. + * @return The common remainder. + */ +[[nodiscard]] inline constexpr std::size_t fast_mod(const std::size_t value, const std::size_t mod) ENTT_NOEXCEPT { + ENTT_ASSERT(is_power_of_two(mod), "Value must be a power of two"); + return value & (mod - 1u); +} + +/** + * @brief Deleter for allocator-aware unique pointers (waiting for C++20). + * @tparam Args Types of arguments to use to construct the object. + */ +template +struct allocation_deleter: private Allocator { + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Pointer type. */ + using pointer = typename std::allocator_traits::pointer; + /** - * @brief Iterates entities and components 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 components. The - * _constness_ of the components 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 - * - * @sa each - * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. + * @brief Inherited constructors. + * @param alloc The allocator to use. */ - template - inline void less(Func func) const { - using non_empty_owned = type_list_cat_t, type_list<>, type_list>...>; - using non_empty_get = type_list_cat_t, type_list<>, type_list>...>; - traverse(std::move(func), non_empty_owned{}, non_empty_get{}); - } + allocation_deleter(const allocator_type &alloc) + : Allocator{alloc} {} /** - * @brief Sort a group according to the given comparison function. - * - * Sort the group so that iterating it with a couple of iterators returns - * entities and components in the expected order. See `begin` and `end` for - * more details. - * - * 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(const Component &..., const Component &...); - * bool(const Entity, const Entity); - * @endcode - * - * Where `Component` 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 oject 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. - * - * The comparison function object received by the sort function object - * hasn't necessarily the type of the one passed along with the other - * parameters to this member function. - * - * @note - * Attempting to iterate elements using a raw pointer returned by a call to - * either `data` or `raw` gives no guarantees on the order, even though - * `sort` has been invoked. - * - * @tparam Component Optional types of components 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. + * @brief Destroys the pointed object and deallocates its memory. + * @param ptr A valid pointer to an object of the given type. */ - template - void sort(Compare compare, Sort algo = Sort{}, Args &&... args) { - std::vector copy(*length); - std::iota(copy.begin(), copy.end(), 0); - - if constexpr(sizeof...(Component) == 0) { - algo(copy.rbegin(), copy.rend(), [compare = std::move(compare), data = data()](const auto lhs, const auto rhs) { - return compare(data[lhs], data[rhs]); - }, std::forward(args)...); - } else { - algo(copy.rbegin(), copy.rend(), [compare = std::move(compare), this](const auto lhs, const auto rhs) { - // useless this-> used to suppress a warning with clang - return compare(this->from_index(lhs)..., this->from_index(rhs)...); - }, std::forward(args)...); - } - - for(size_type pos{}, last = copy.size(); pos < last; ++pos) { - auto curr = pos; - auto next = copy[curr]; - - while(curr != next) { - const auto lhs = copy[curr]; - const auto rhs = copy[next]; - (swap(0, lhs, rhs), ...); - copy[curr] = curr; - curr = next; - next = copy[curr]; - } - } + void operator()(pointer ptr) { + using alloc_traits = typename std::allocator_traits; + alloc_traits::destroy(*this, to_address(ptr)); + alloc_traits::deallocate(*this, ptr, 1u); } - -private: - const typename basic_registry::size_type *length; - const std::tuple *..., pool_type *...> pools; }; +/** + * @brief Allows `std::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 +auto allocate_unique(Allocator &allocator, Args &&...args) { + static_assert(!std::is_array_v, "Array types are not supported"); -} + using alloc_traits = typename std::allocator_traits::template rebind_traits; + using allocator_type = typename alloc_traits::allocator_type; + allocator_type alloc{allocator}; + auto ptr = alloc_traits::allocate(alloc, 1u); -#endif // ENTT_ENTITY_GROUP_HPP + ENTT_TRY { + alloc_traits::construct(alloc, to_address(ptr), std::forward(args)...); + } + ENTT_CATCH { + alloc_traits::deallocate(alloc, ptr, 1u); + ENTT_THROW; + } -// #include "view.hpp" -#ifndef ENTT_ENTITY_VIEW_HPP -#define ENTT_ENTITY_VIEW_HPP + return std::unique_ptr>{ptr, alloc}; +} +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ -#include -#include -#include -#include -#include -#include -// #include "../config/config.h" +namespace internal { -// #include "../core/type_traits.hpp" +template +struct uses_allocator_construction { + template + static constexpr auto args([[maybe_unused]] const Allocator &allocator, Params &&...params) ENTT_NOEXCEPT { + if constexpr(!std::uses_allocator_v && std::is_constructible_v) { + return std::forward_as_tuple(std::forward(params)...); + } else { + static_assert(std::uses_allocator_v, "Ill-formed request"); -// #include "sparse_set.hpp" + if constexpr(std::is_constructible_v) { + return std::tuple(std::allocator_arg, allocator, std::forward(params)...); + } else { + static_assert(std::is_constructible_v, "Ill-formed request"); + return std::forward_as_tuple(std::forward(params)..., allocator); + } + } + } +}; -// #include "storage.hpp" +template +struct uses_allocator_construction> { + using type = std::pair; -// #include "entity.hpp" + template + static constexpr auto args(const Allocator &allocator, std::piecewise_construct_t, First &&first, Second &&second) ENTT_NOEXCEPT { + return std::make_tuple( + std::piecewise_construct, + std::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, std::forward(curr)...); }, std::forward(first)), + std::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, std::forward(curr)...); }, std::forward(second))); + } -// #include "fwd.hpp" + template + static constexpr auto args(const Allocator &allocator) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::tuple<>{}, std::tuple<>{}); + } + template + static constexpr auto args(const Allocator &allocator, First &&first, Second &&second) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::forward(first)), std::forward_as_tuple(std::forward(second))); + } + template + static constexpr auto args(const Allocator &allocator, const std::pair &value) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(value.first), std::forward_as_tuple(value.second)); + } -namespace entt { + template + static constexpr auto args(const Allocator &allocator, std::pair &&value) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::move(value.first)), std::forward_as_tuple(std::move(value.second))); + } +}; +} // namespace internal /** - * @brief Multi component view. - * - * Multi component views iterate over those entities that have at least all the - * given components in their bags. During initialization, a multi component view - * looks at the number of entities available for each component and picks up a - * reference to the smallest set of candidate entities in order to get a - * performance boost when iterate.
- * Order of elements during iterations are highly dependent on the order of the - * underlying data structures. See sparse_set and its specializations for more - * details. - * - * @b Important + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Uses-allocator construction utility (waiting for C++20). * - * Iterators aren't invalidated if: + * Primarily intended for internal use. Prepares the argument list needed to + * create an object of a given type by means of uses-allocator construction. * - * * New instances of the given components are created and assigned to entities. - * * The entity currently pointed is modified (as an example, if one of the - * given components is removed from the entity to which the iterator points). + * @tparam Type Type to return arguments for. + * @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 The arguments needed to create an object of the given type. + */ +template +constexpr auto uses_allocator_construction_args(const Allocator &allocator, Args &&...args) ENTT_NOEXCEPT { + return internal::uses_allocator_construction::args(allocator, std::forward(args)...); +} + +/** + * @brief Uses-allocator construction utility (waiting for C++20). * - * In all the other cases, modifying the pools of the given components in any - * way invalidates all the iterators and using them results in undefined - * behavior. + * Primarily intended for internal use. Creates an object of a given type by + * means of uses-allocator construction. * - * @note - * Views share references to the underlying data structures of the registry that - * generated them. Therefore any change to the entities and to the components - * made by means of the registry are immediately reflected by views. + * @tparam Type Type of object to create. + * @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 newly created object of the given type. + */ +template +constexpr Type make_obj_using_allocator(const Allocator &allocator, Args &&...args) { + return std::make_from_tuple(internal::uses_allocator_construction::args(allocator, std::forward(args)...)); +} + +/** + * @brief Uses-allocator construction utility (waiting for C++20). * - * @warning - * Lifetime of a view must overcome the one of the registry that generated it. - * In any other case, attempting to use a view results in undefined behavior. + * Primarily intended for internal use. Creates an object of a given type by + * means of uses-allocator construction at an uninitialized memory location. * - * @tparam Entity A valid entity type (see entt_traits for more details). - * @tparam Component Types of components iterated by the view. + * @tparam Type Type of object to create. + * @tparam Allocator Type of allocator used to manage memory and elements. + * @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 -class basic_view { - static_assert(sizeof...(Component) > 1); +template +constexpr Type *uninitialized_construct_using_allocator(Type *value, const Allocator &allocator, Args &&...args) { + return std::apply([&](auto &&...curr) { return new(value) Type(std::forward(curr)...); }, internal::uses_allocator_construction::args(allocator, std::forward(args)...)); +} - /*! @brief A registry is allowed to create views. */ - friend class basic_registry; +} // namespace entt - template - using pool_type = std::conditional_t, const storage>, storage>; +#endif - template - using component_iterator_type = decltype(std::declval>().begin()); +// #include "core/monostate.hpp" +#ifndef ENTT_CORE_MONOSTATE_HPP +#define ENTT_CORE_MONOSTATE_HPP - using underlying_iterator_type = typename sparse_set::iterator_type; - using unchecked_type = std::array *, (sizeof...(Component) - 1)>; - using traits_type = entt_traits; +// #include "../config/config.h" - class iterator { - friend class basic_view; +// #include "fwd.hpp" - using extent_type = typename sparse_set::size_type; - iterator(unchecked_type other, underlying_iterator_type first, underlying_iterator_type last) ENTT_NOEXCEPT - : unchecked{other}, - begin{first}, - end{last}, - extent{min(std::make_index_sequence{})} - { - if(begin != end && !valid()) { - ++(*this); - } - } +namespace entt { - template - extent_type min(std::index_sequence) const ENTT_NOEXCEPT { - return std::min({ std::get(unchecked)->extent()... }); - } +/** + * @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. + */ + template + void operator=(Type val) const ENTT_NOEXCEPT { + value = val; + } - bool valid() const ENTT_NOEXCEPT { - const auto entt = *begin; - const auto sz = size_type(entt& traits_type::entity_mask); + /** + * @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 ENTT_NOEXCEPT { + return value; + } - return sz < extent && std::all_of(unchecked.cbegin(), unchecked.cend(), [entt](const sparse_set *view) { - return view->has(entt); - }); - } +private: + template + inline static ENTT_MAYBE_ATOMIC(Type) value{}; +}; - public: - using difference_type = typename underlying_iterator_type::difference_type; - using value_type = typename underlying_iterator_type::value_type; - using pointer = typename underlying_iterator_type::pointer; - using reference = typename underlying_iterator_type::reference; - using iterator_category = std::forward_iterator_tag; +/** + * @brief Helper variable template. + * @tparam Value Value used to differentiate between different variables. + */ +template +inline monostate monostate_v = {}; - iterator() ENTT_NOEXCEPT = default; +} // namespace entt - iterator & operator++() ENTT_NOEXCEPT { - return (++begin != end && !valid()) ? ++(*this) : *this; - } +#endif - iterator operator++(int) ENTT_NOEXCEPT { - iterator orig = *this; - return ++(*this), orig; - } +// #include "core/tuple.hpp" +#ifndef ENTT_CORE_TUPLE_HPP +#define ENTT_CORE_TUPLE_HPP - bool operator==(const iterator &other) const ENTT_NOEXCEPT { - return other.begin == begin; - } +#include +#include +#include +// #include "../config/config.h" - inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this == other); - } - pointer operator->() const ENTT_NOEXCEPT { - return begin.operator->(); - } +namespace entt { - inline reference operator*() const ENTT_NOEXCEPT { - return *operator->(); - } +/** + * @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) ENTT_NOEXCEPT { + if constexpr(std::tuple_size_v> == 1u) { + return std::get<0>(std::forward(value)); + } else { + return std::forward(value); + } +} - private: - unchecked_type unchecked; - underlying_iterator_type begin; - underlying_iterator_type end; - extent_type extent; - }; +} // namespace entt - // we could use pool_type *..., but vs complains about it and refuses to compile for unknown reasons (likely a bug) - basic_view(storage> *... ref) ENTT_NOEXCEPT - : pools{ref...} - {} +#endif - const sparse_set * candidate() const ENTT_NOEXCEPT { - return std::min({ static_cast *>(std::get *>(pools))... }, [](const auto *lhs, const auto *rhs) { - return lhs->size() < rhs->size(); - }); - } +// #include "core/type_info.hpp" +#ifndef ENTT_CORE_TYPE_INFO_HPP +#define ENTT_CORE_TYPE_INFO_HPP - unchecked_type unchecked(const sparse_set *view) const ENTT_NOEXCEPT { - unchecked_type other{}; - typename unchecked_type::size_type pos{}; - ((std::get *>(pools) == view ? nullptr : (other[pos++] = std::get *>(pools))), ...); - return other; - } +#include +#include +#include +// #include "../config/config.h" - template - inline decltype(auto) get([[maybe_unused]] component_iterator_type it, [[maybe_unused]] pool_type *cpool, [[maybe_unused]] const Entity entt) const ENTT_NOEXCEPT { - if constexpr(std::is_same_v) { - return *it; - } else { - return cpool->get(entt); - } - } +// #include "../core/attribute.h" - template - void traverse(Func func, type_list, type_list) const { - const auto end = std::get *>(pools)->sparse_set::end(); - auto begin = std::get *>(pools)->sparse_set::begin(); +// #include "fwd.hpp" - if constexpr(std::disjunction_v...>) { - std::for_each(begin, end, [raw = std::get *>(pools)->begin(), &func, this](const auto entity) mutable { - auto curr = raw++; +// #include "hashed_string.hpp" - if((std::get *>(pools)->has(entity) && ...)) { - if constexpr(std::is_invocable_v({}))...>) { - func(get(curr, std::get *>(pools), entity)...); - } else { - func(entity, get(curr, std::get *>(pools), entity)...); - } - } - }); - } else { - std::for_each(begin, end, [&func, this](const auto entity) mutable { - if((std::get *>(pools)->has(entity) && ...)) { - if constexpr(std::is_invocable_v({}))...>) { - func(std::get *>(pools)->get(entity)...); - } else { - func(entity, std::get *>(pools)->get(entity)...); - } - } - }); - } - } -public: - /*! @brief Underlying entity identifier. */ - using entity_type = typename sparse_set::entity_type; - /*! @brief Unsigned integer type. */ - using size_type = typename sparse_set::size_type; - /*! @brief Input iterator type. */ - using iterator_type = iterator; +namespace entt { - /** - * @brief Returns the number of existing components of the given type. - * @tparam Comp Type of component of which to return the size. - * @return Number of existing components of the given type. - */ - template - size_type size() const ENTT_NOEXCEPT { - return std::get *>(pools)->size(); - } +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ - /** - * @brief Estimates the number of entities that have the given components. - * @return Estimated number of entities that have the given components. - */ - size_type size() const ENTT_NOEXCEPT { - return std::min({ std::get *>(pools)->size()... }); - } +namespace internal { - /** - * @brief Checks whether the pool of a given component is empty. - * @tparam Comp Type of component in which one is interested. - * @return True if the pool of the given component is empty, false - * otherwise. - */ - template - bool empty() const ENTT_NOEXCEPT { - return std::get *>(pools)->empty(); +struct ENTT_API type_index final { + [[nodiscard]] static id_type next() ENTT_NOEXCEPT { + static ENTT_MAYBE_ATOMIC(id_type) value{}; + return value++; } +}; - /** - * @brief Checks if the view is definitely empty. - * @return True if the view is definitely empty, false otherwise. - */ - bool empty() const ENTT_NOEXCEPT { - return (std::get *>(pools)->empty() || ...); - } +template +[[nodiscard]] constexpr auto stripped_type_name() ENTT_NOEXCEPT { +#if defined ENTT_PRETTY_FUNCTION + std::string_view pretty_function{ENTT_PRETTY_FUNCTION}; + auto first = pretty_function.find_first_not_of(' ', pretty_function.find_first_of(ENTT_PRETTY_FUNCTION_PREFIX) + 1); + auto value = pretty_function.substr(first, pretty_function.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first); + return value; +#else + return std::string_view{""}; +#endif +} - /** - * @brief Direct access to the list of components of a given pool. - * - * The returned pointer is such that range - * `[raw(), raw() + size()]` is always a valid range, even - * if the container is empty. - * - * @note - * There are no guarantees on the order of the components. Use `begin` and - * `end` if you want to iterate the view in the expected order. - * - * @tparam Comp Type of component in which one is interested. - * @return A pointer to the array of components. - */ - template - Comp * raw() const ENTT_NOEXCEPT { - return std::get *>(pools)->raw(); - } +template().find_first_of('.')> +[[nodiscard]] static constexpr std::string_view type_name(int) ENTT_NOEXCEPT { + constexpr auto value = stripped_type_name(); + return value; +} - /** - * @brief Direct access to the list of entities of a given pool. - * - * The returned pointer is such that range - * `[data(), data() + size()]` is always a valid range, - * even if the container is empty. - * - * @note - * There are no guarantees on the order of the entities. Use `begin` and - * `end` if you want to iterate the view in the expected order. - * - * @tparam Comp Type of component in which one is interested. - * @return A pointer to the array of entities. - */ - template - const entity_type * data() const ENTT_NOEXCEPT { - return std::get *>(pools)->data(); - } +template +[[nodiscard]] static std::string_view type_name(char) ENTT_NOEXCEPT { + static const auto value = stripped_type_name(); + return value; +} + +template().find_first_of('.')> +[[nodiscard]] static constexpr id_type type_hash(int) ENTT_NOEXCEPT { + constexpr auto stripped = stripped_type_name(); + constexpr auto value = hashed_string::value(stripped.data(), stripped.size()); + return value; +} + +template +[[nodiscard]] static id_type type_hash(char) ENTT_NOEXCEPT { + static const auto value = [](const auto stripped) { + return hashed_string::value(stripped.data(), stripped.size()); + }(stripped_type_name()); + return value; +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ +/** + * @brief Type sequential identifier. + * @tparam Type Type for which to generate a sequential identifier. + */ +template +struct ENTT_API type_index final { /** - * @brief Returns an iterator to the first entity that has the given - * components. - * - * The returned iterator points to the first entity that has the given - * components. If the view is empty, the returned iterator will be equal to - * `end()`. - * - * @note - * Input iterators stay true to the order imposed to the underlying data - * structures. - * - * @return An iterator to the first entity that has the given components. + * @brief Returns the sequential identifier of a given type. + * @return The sequential identifier of a given type. */ - iterator_type begin() const ENTT_NOEXCEPT { - const auto *view = candidate(); - return iterator_type{unchecked(view), view->begin(), view->end()}; + [[nodiscard]] static id_type value() ENTT_NOEXCEPT { + static const id_type value = internal::type_index::next(); + return value; } - /** - * @brief Returns an iterator that is past the last entity that has the - * given components. - * - * The returned iterator points to the entity following the last entity that - * has the given components. Attempting to dereference the returned iterator - * results in undefined behavior. - * - * @note - * Input iterators stay true to the order imposed to the underlying data - * structures. - * - * @return An iterator to the entity following the last entity that has the - * given components. - */ - iterator_type end() const ENTT_NOEXCEPT { - const auto *view = candidate(); - return iterator_type{unchecked(view), view->end(), view->end()}; + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const ENTT_NOEXCEPT { + return value(); } +}; +/** + * @brief Type hash. + * @tparam Type Type for which to generate a hash value. + */ +template +struct type_hash final { /** - * @brief Finds an entity. - * @param entt A valid entity identifier. - * @return An iterator to the given entity if it's found, past the end - * iterator otherwise. + * @brief Returns the numeric representation of a given type. + * @return The numeric representation of the given type. */ - iterator_type find(const entity_type entt) const ENTT_NOEXCEPT { - const auto *view = candidate(); - iterator_type it{unchecked(view), view->find(entt), view->end()}; - return (it != end() && *it == entt) ? it : end(); +#if defined ENTT_PRETTY_FUNCTION + [[nodiscard]] static constexpr id_type value() ENTT_NOEXCEPT { + return internal::type_hash(0); +#else + [[nodiscard]] static constexpr id_type value() ENTT_NOEXCEPT { + return type_index::value(); +#endif } - /** - * @brief Checks if a view contains an entity. - * @param entt A valid entity identifier. - * @return True if the view contains the given entity, false otherwise. - */ - bool contains(const entity_type entt) const ENTT_NOEXCEPT { - return find(entt) != end(); + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const ENTT_NOEXCEPT { + return value(); } +}; +/** + * @brief Type name. + * @tparam Type Type for which to generate a name. + */ +template +struct type_name final { /** - * @brief Returns the components assigned to the given entity. - * - * Prefer this function instead of `registry::get` during iterations. It has - * far better performance than its companion function. - * - * @warning - * Attempting to use an invalid component type results in a compilation - * error. Attempting to use an entity that doesn't belong to the view - * results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * view doesn't contain the given entity. - * - * @tparam Comp Types of components to get. - * @param entt A valid entity identifier. - * @return The components assigned to the entity. + * @brief Returns the name of a given type. + * @return The name of the given type. */ - template - decltype(auto) get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { - ENTT_ASSERT(contains(entt)); + [[nodiscard]] static constexpr std::string_view value() ENTT_NOEXCEPT { + return internal::type_name(0); + } - if constexpr(sizeof...(Comp) == 1) { - return (std::get *>(pools)->get(entt), ...); - } else { - return std::tuple(entt))...>{get(entt)...}; - } + /*! @copydoc value */ + [[nodiscard]] constexpr operator std::string_view() const ENTT_NOEXCEPT { + return value(); } +}; +/*! @brief Implementation specific information about a type. */ +struct type_info final { /** - * @brief Iterates entities and components 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 all its components. The - * _constness_ of the components is as requested.
- * The signature of the function must be equivalent to one of the following - * forms: - * - * @code{.cpp} - * void(const entity_type, Component &...); - * void(Component &...); - * @endcode - * - * @note - * Empty types aren't explicitly instantiated. Therefore, temporary objects - * are returned during iterations. They can be caught only by copy or with - * const references. - * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. + * @brief Constructs a type info object for a given type. + * @tparam Type Type for which to construct a type info object. */ - template - inline void each(Func func) const { - const auto *view = candidate(); - ((std::get *>(pools) == view ? each(std::move(func)) : void()), ...); - } + template + constexpr type_info(std::in_place_type_t) ENTT_NOEXCEPT + : seq{type_index>>::value()}, + identifier{type_hash>>::value()}, + alias{type_name>>::value()} {} /** - * @brief Iterates entities and components 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 all its components. The - * _constness_ of the components is as requested.
- * The signature of the function must be equivalent to one of the following - * forms: - * - * @code{.cpp} - * void(const entity_type, Component &...); - * void(Component &...); - * @endcode - * - * The pool of the suggested component is used to lead the iterations. The - * returned entities will therefore respect the order of the pool associated - * with that type.
- * It is no longer guaranteed that the performance is the best possible, but - * there will be greater control over the order of iteration. - * - * @note - * Empty types aren't explicitly instantiated. Therefore, temporary objects - * are returned during iterations. They can be caught only by copy or with - * const references. - * - * @tparam Comp Type of component to use to enforce the iteration order. - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. + * @brief Type index. + * @return Type index. */ - template - inline void each(Func func) const { - using other_type = type_list_cat_t, type_list<>, type_list>...>; - traverse(std::move(func), other_type{}, type_list{}); + [[nodiscard]] constexpr id_type index() const ENTT_NOEXCEPT { + return seq; } /** - * @brief Iterates entities and components 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 components. The - * _constness_ of the components 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 - * - * @sa each - * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. + * @brief Type hash. + * @return Type hash. */ - template - inline void less(Func func) const { - const auto *view = candidate(); - ((std::get *>(pools) == view ? less(std::move(func)) : void()), ...); + [[nodiscard]] constexpr id_type hash() const ENTT_NOEXCEPT { + return identifier; } /** - * @brief Iterates entities and components 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 components. The - * _constness_ of the components 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 - * - * The pool of the suggested component is used to lead the iterations. The - * returned entities will therefore respect the order of the pool associated - * with that type.
- * It is no longer guaranteed that the performance is the best possible, but - * there will be greater control over the order of iteration. - * - * @sa each - * - * @tparam Comp Type of component to use to enforce the iteration order. - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. + * @brief Type name. + * @return Type name. */ - template - inline void less(Func func) const { - using other_type = type_list_cat_t, type_list<>, type_list>...>; - using non_empty_type = type_list_cat_t, type_list<>, type_list>...>; - traverse(std::move(func), other_type{}, non_empty_type{}); + [[nodiscard]] constexpr std::string_view name() const ENTT_NOEXCEPT { + return alias; } private: - const std::tuple *...> pools; + id_type seq; + id_type identifier; + std::string_view alias; }; +/** + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects are identical, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator==(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return lhs.hash() == rhs.hash(); +} /** - * @brief Single component view specialization. - * - * Single component views are specialized in order to get a boost in terms of - * performance. This kind of views can access the underlying data structure - * directly and avoid superfluous checks.
- * Order of elements during iterations are highly dependent on the order of the - * underlying data structure. See sparse_set and its specializations for more - * details. - * - * @b Important - * - * Iterators aren't invalidated if: - * - * * New instances of the given component are created and assigned to entities. - * * The entity currently pointed is modified (as an example, the given - * component is removed from the entity to which the iterator points). - * - * In all the other cases, modifying the pool of the given component in any way - * invalidates all the iterators and using them results in undefined behavior. - * - * @note - * Views share a reference to the underlying data structure of the registry that - * generated them. Therefore any change to the entities and to the components - * made by means of the registry are immediately reflected by views. + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects differ, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator!=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than the second, false otherwise. + */ +[[nodiscard]] constexpr bool operator<(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return lhs.index() < rhs.index(); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than or equal to the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator<=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(rhs < lhs); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator>(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than or equal to the second, + * false otherwise. + */ +[[nodiscard]] constexpr bool operator>=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +/** + * @brief Returns the type info object associated to a given type. * - * @warning - * Lifetime of a view must overcome the one of the registry that generated it. - * In any other case, attempting to use a view results in undefined behavior. + * 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 Entity A valid entity type (see entt_traits for more details). - * @tparam Component Type of component iterated by the view. + * @tparam Type Type for which to generate a type info object. + * @return A reference to a properly initialized type info object. */ -template -class basic_view { - /*! @brief A registry is allowed to create views. */ - friend class basic_registry; +template +[[nodiscard]] const type_info &type_id() ENTT_NOEXCEPT { + if constexpr(std::is_same_v>>) { + static type_info instance{std::in_place_type}; + return instance; + } else { + return type_id>>(); + } +} - using pool_type = std::conditional_t, const storage>, storage>; +/*! @copydoc type_id */ +template +[[nodiscard]] const type_info &type_id(Type &&) ENTT_NOEXCEPT { + return type_id>>(); +} - basic_view(pool_type *ref) ENTT_NOEXCEPT - : pool{ref} - {} +} // namespace entt -public: - /*! @brief Type of component iterated by the view. */ - using raw_type = Component; - /*! @brief Underlying entity identifier. */ - using entity_type = typename pool_type::entity_type; - /*! @brief Unsigned integer type. */ - using size_type = typename pool_type::size_type; - /*! @brief Input iterator type. */ - using iterator_type = typename sparse_set::iterator_type; +#endif - /** - * @brief Returns the number of entities that have the given component. - * @return Number of entities that have the given component. - */ - size_type size() const ENTT_NOEXCEPT { - return pool->size(); - } - - /** - * @brief Checks whether the view is empty. - * @return True if the view is empty, false otherwise. - */ - bool empty() const ENTT_NOEXCEPT { - return pool->empty(); - } - - /** - * @brief Direct access to the list of components. - * - * The returned pointer is such that range `[raw(), raw() + size()]` is - * always a valid range, even if the container is empty. - * - * @note - * There are no guarantees on the order of the components. Use `begin` and - * `end` if you want to iterate the view in the expected order. - * - * @return A pointer to the array of components. - */ - raw_type * raw() const ENTT_NOEXCEPT { - return pool->raw(); - } - - /** - * @brief Direct access to the list of entities. - * - * The returned pointer is such that range `[data(), data() + size()]` is - * always a valid range, even if the container is empty. - * - * @note - * There are no guarantees on the order of the entities. Use `begin` and - * `end` if you want to iterate the view in the expected order. - * - * @return A pointer to the array of entities. - */ - const entity_type * data() const ENTT_NOEXCEPT { - return pool->data(); - } - - /** - * @brief Returns an iterator to the first entity that has the given - * component. - * - * The returned iterator points to the first entity that has the given - * component. If the view is empty, the returned iterator will be equal to - * `end()`. - * - * @note - * Input iterators stay true to the order imposed to the underlying data - * structures. - * - * @return An iterator to the first entity that has the given component. - */ - iterator_type begin() const ENTT_NOEXCEPT { - return pool->sparse_set::begin(); - } +// #include "core/type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP - /** - * @brief Returns an iterator that is past the last entity that has the - * given component. - * - * The returned iterator points to the entity following the last entity that - * has the given component. Attempting to dereference the returned iterator - * results in undefined behavior. - * - * @note - * Input iterators stay true to the order imposed to the underlying data - * structures. - * - * @return An iterator to the entity following the last entity that has the - * given component. - */ - iterator_type end() const ENTT_NOEXCEPT { - return pool->sparse_set::end(); - } +#include +#include +#include +#include +// #include "../config/config.h" - /** - * @brief Finds an entity. - * @param entt A valid entity identifier. - * @return An iterator to the given entity if it's found, past the end - * iterator otherwise. - */ - iterator_type find(const entity_type entt) const ENTT_NOEXCEPT { - const auto it = pool->find(entt); - return it != end() && *it == entt ? it : end(); - } +// #include "fwd.hpp" - /** - * @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. - */ - entity_type operator[](const size_type pos) const ENTT_NOEXCEPT { - return begin()[pos]; - } - /** - * @brief Checks if a view contains an entity. - * @param entt A valid entity identifier. - * @return True if the view contains the given entity, false otherwise. - */ - bool contains(const entity_type entt) const ENTT_NOEXCEPT { - return find(entt) != end(); - } +namespace entt { - /** - * @brief Returns the component assigned to the given entity. - * - * Prefer this function instead of `registry::get` during iterations. It has - * far better performance than its companion function. - * - * @warning - * Attempting to use an entity that doesn't belong to the view results in - * undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * view doesn't contain the given entity. - * - * @param entt A valid entity identifier. - * @return The component assigned to the entity. - */ - decltype(auto) get(const entity_type entt) const ENTT_NOEXCEPT { - ENTT_ASSERT(contains(entt)); - return pool->get(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 TURN_OFF_DOXYGEN */ choice_t /*! @endcond */ +{}; - /** - * @brief Iterates entities and components 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 reference to its component. The _constness_ of the - * component is as requested.
- * The signature of the function must be equivalent to one of the following - * forms: - * - * @code{.cpp} - * void(const entity_type, Component &); - * void(Component &); - * @endcode - * - * @note - * Empty types aren't explicitly instantiated. Therefore, temporary objects - * are returned during iterations. They can be caught only by copy or with - * const references. - * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. - */ - template - inline void each(Func func) const { - if constexpr(std::is_invocable_v) { - std::for_each(pool->begin(), pool->end(), std::move(func)); - } else { - std::for_each(pool->sparse_set::begin(), pool->sparse_set::end(), [&func, raw = pool->begin()](const auto entt) mutable { - func(entt, *(raw++)); - }); - } - } +/*! @copybrief choice_t */ +template<> +struct choice_t<0> {}; - /** - * @brief Iterates entities and components 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 reference to its component if it's a non-empty one. - * The _constness_ of the component is as requested.
- * The signature of the function must be equivalent to one of the following - * forms in case the component isn't an empty one: - * - * @code{.cpp} - * void(const entity_type, Component &); - * void(Component &); - * @endcode - * - * In case the component is an empty one instead, the following forms are - * accepted: - * - * @code{.cpp} - * void(const entity_type); - * void(); - * @endcode - * - * @sa each - * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. - */ - template - inline void less(Func func) const { - if constexpr(std::is_empty_v) { - if constexpr(std::is_invocable_v) { - for(auto pos = pool->size(); pos; --pos) { - func(); - } - } else { - std::for_each(pool->sparse_set::begin(), pool->sparse_set::end(), std::move(func)); - } - } else { - each(std::move(func)); - } - } +/** + * @brief Variable template for the choice trick. + * @tparam N Number of choices available. + */ +template +inline constexpr choice_t choice{}; -private: - pool_type *pool; +/** + * @brief Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; }; +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; -} - +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; -#endif // ENTT_ENTITY_VIEW_HPP +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; -// #include "fwd.hpp" +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::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; -namespace entt { +/** + * @brief Wraps a static constant. + * @tparam Value A static constant. + */ +template +using integral_constant = std::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 Alias for exclusion lists. - * @tparam Type List of types. + * @brief A class to use to push around lists of types, nothing more. + * @tparam Type Types provided by the type list. */ template -struct exclude_t: type_list {}; +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 Variable template for exclusion lists. - * @tparam Type List of types. + * @brief Provides compile-time indexed access to the types of a type list. + * @tparam Index Index of the type to return. + * @tparam Type First type provided by the type list. + * @tparam Other Other types provided by the type list. */ -template -constexpr exclude_t exclude{}; - +template +struct type_list_element> + : type_list_element> {}; /** - * @brief Fast and reliable entity-component system. - * - * The registry is the core class of the entity-component framework.
- * It stores entities and arranges pools of components on a per request basis. - * By means of a registry, users can manage entities and components and thus - * create views or groups to iterate them. - * - * @tparam Entity A valid entity type (see entt_traits for more details). + * @brief Provides compile-time indexed access to the types of a type list. + * @tparam Type First type provided by the type list. + * @tparam Other Other types provided by the type list. */ -template -class basic_registry { - using context_family = family; - using component_family = family; - using traits_type = entt_traits; +template +struct type_list_element<0u, type_list> { + /*! @brief Searched type. */ + using type = Type; +}; - template - struct pool_handler: storage { - using reference_type = std::conditional_t, const Component &, Component &>; - - sigh on_construct; - sigh on_replace; - sigh on_destroy; - void *group{}; - - pool_handler() ENTT_NOEXCEPT = default; - - pool_handler(const storage &other) - : storage{other} - {} - - template - decltype(auto) assign(basic_registry ®istry, const Entity entt, Args &&... args) { - if constexpr(std::is_empty_v) { - storage::construct(entt); - on_construct.publish(registry, entt, Component{}); - return Component{std::forward(args)...}; - } else { - auto &component = storage::construct(entt, std::forward(args)...); - on_construct.publish(registry, entt, component); - return component; - } - } +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; - template - Component * batch(basic_registry ®istry, It first, It last) { - Component *component = nullptr; +/** + * @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 +constexpr type_list operator+(type_list, type_list) { + return {}; +} - if constexpr(std::is_empty_v) { - storage::batch(first, last); +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_cat; - if(!on_construct.empty()) { - std::for_each(first, last, [®istry, this](const auto entt) { - on_construct.publish(registry, entt, Component{}); - }); - } - } else { - component = storage::batch(first, last); +/*! @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<>; +}; - if(!on_construct.empty()) { - std::for_each(first, last, [®istry, component, this](const auto entt) mutable { - on_construct.publish(registry, entt, *(component++)); - }); - } - } +/** + * @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 = typename type_list_cat, List...>::type; +}; - return component; - } +/** + * @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; +}; - void remove(basic_registry ®istry, const Entity entt) { - on_destroy.publish(registry, entt); - storage::destroy(entt); - } +/** + * @brief Helper type. + * @tparam List Type lists to concatenate. + */ +template +using type_list_cat_t = typename type_list_cat::type; - template - decltype(auto) replace(basic_registry ®istry, const Entity entt, Args &&... args) { - if constexpr(std::is_empty_v) { - ENTT_ASSERT((storage::has(entt))); - on_replace.publish(registry, entt, Component{}); - return Component{std::forward(args)...}; - } else { - Component component{std::forward(args)...}; - on_replace.publish(registry, entt, component); - return (storage::get(entt) = std::move(component)); - } - } - }; +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; - template - using pool_type = pool_handler>; +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; +}; - template - struct group_handler; +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; - template - struct group_handler, type_list>: sparse_set { - template - void maybe_valid_if(basic_registry ®, const Entity entt, const Args &...) { - if constexpr(std::disjunction_v...>) { - if(((std::is_same_v || reg.pool()->has(entt)) && ...) && !(reg.pool()->has(entt) || ...)) { - this->construct(entt); - } - } else if constexpr(std::disjunction_v...>) { - if((reg.pool()->has(entt) && ...) && ((std::is_same_v || !reg.pool()->has(entt)) && ...)) { - this->construct(entt); - } - } - } +/** + * @brief Helper type. + * @tparam Type A type list. + */ +template +using type_list_unique_t = typename type_list_unique::type; - template - void discard_if(basic_registry &, const Entity entt, const Args &...) { - if(this->has(entt)) { - this->destroy(entt); - } - } - }; +/** + * @brief Provides the member constant `value` 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; - template - struct group_handler, type_list, Owned...>: sparse_set { - std::size_t owned{}; - - template - void maybe_valid_if(basic_registry ®, const Entity entt, const Args &...) { - const auto cpools = std::make_tuple(reg.pool()...); - - if constexpr(std::disjunction_v..., std::is_same...>) { - if(((std::is_same_v || std::get *>(cpools)->has(entt)) && ...) - && ((std::is_same_v || reg.pool()->has(entt)) && ...) - && !(reg.pool()->has(entt) || ...)) - { - const auto pos = this->owned++; - (reg.swap(0, std::get *>(cpools), entt, pos), ...); - } - } else if constexpr(std::disjunction_v...>) { - if((std::get *>(cpools)->has(entt) && ...) - && (reg.pool()->has(entt) && ...) - && ((std::is_same_v || !reg.pool()->has(entt)) && ...)) - { - const auto pos = this->owned++; - (reg.swap(0, std::get *>(cpools), entt, pos), ...); - } - } - } +/** + * @copybrief type_list_contains + * @tparam Type Types provided by the type list. + * @tparam Other Type to look for. + */ +template +struct type_list_contains, Other>: std::disjunction...> {}; - template - void discard_if(basic_registry ®, const Entity entt, const Args &...) { - const auto cpools = std::make_tuple(reg.pool()...); +/** + * @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; - if(std::get<0>(cpools)->has(entt) && std::get<0>(cpools)->sparse_set::get(entt) < this->owned) { - const auto pos = --this->owned; - (reg.swap(0, std::get *>(cpools), entt, pos), ...); - } - } - }; +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_diff; - struct pool_data { - std::unique_ptr> pool; - std::unique_ptr> (* clone)(const sparse_set &); - void (* remove)(basic_registry &, const Entity); - ENTT_ID_TYPE runtime_type; - }; +/** + * @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>...>; +}; - struct group_data { - const std::size_t extent[3]; - std::unique_ptr group; - bool(* const is_same)(const ENTT_ID_TYPE *); - }; +/** + * @brief Helper type. + * @tparam List Type lists between which to compute the difference. + */ +template +using type_list_diff_t = typename type_list_diff::type; - struct ctx_variable { - std::unique_ptr value; - ENTT_ID_TYPE runtime_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); +}; - template - static ENTT_ID_TYPE runtime_type() ENTT_NOEXCEPT { - if constexpr(is_named_type_v) { - return named_type_traits::value; - } else { - return Family::template type; - } - } +/*! @brief Primary template isn't defined on purpose. */ +template +struct value_list_element; - void release(const Entity entity) { - // lengthens the implicit list of destroyed entities - const auto entt = entity & traits_type::entity_mask; - const auto version = ((entity >> traits_type::entity_shift) + 1) << traits_type::entity_shift; - const auto node = (available ? next : ((entt + 1) & traits_type::entity_mask)) | version; - entities[entt] = node; - next = entt; - ++available; - } +/** + * @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> {}; - template - inline auto swap(int, pool_type *cpool, const Entity entt, const std::size_t pos) - -> decltype(std::swap(cpool->get(entt), cpool->raw()[pos]), void()) { - std::swap(cpool->get(entt), cpool->raw()[pos]); - cpool->swap(cpool->sparse_set::get(entt), pos); - } +/** + * @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 value. */ + static constexpr auto value = Value; +}; - template - inline void swap(char, pool_type *cpool, const Entity entt, const std::size_t pos) { - cpool->swap(cpool->sparse_set::get(entt), pos); - } +/** + * @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; - template - inline const auto * pool() const ENTT_NOEXCEPT { - const auto ctype = type(); +/** + * @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 +constexpr value_list operator+(value_list, value_list) { + return {}; +} - if constexpr(is_named_type_v) { - const auto it = std::find_if(pools.begin()+skip_family_pools, pools.end(), [ctype](const auto &candidate) { - return candidate.runtime_type == ctype; - }); +/*! @brief Primary template isn't defined on purpose. */ +template +struct value_list_cat; - return it == pools.cend() ? nullptr : static_cast *>(it->pool.get()); - } else { - return ctype < skip_family_pools ? static_cast *>(pools[ctype].pool.get()) : nullptr; - } - } +/*! @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<>; +}; - template - inline auto * pool() ENTT_NOEXCEPT { - return const_cast *>(std::as_const(*this).template pool()); - } +/** + * @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 = typename value_list_cat, List...>::type; +}; - template - auto * assure() { - const auto ctype = type(); - pool_data *pdata = nullptr; +/** + * @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; +}; - if constexpr(is_named_type_v) { - const auto it = std::find_if(pools.begin()+skip_family_pools, pools.end(), [ctype](const auto &candidate) { - return candidate.runtime_type == ctype; - }); +/** + * @brief Helper type. + * @tparam List Value lists to concatenate. + */ +template +using value_list_cat_t = typename value_list_cat::type; - pdata = (it == pools.cend() ? &pools.emplace_back() : &(*it)); - } else { - if(!(ctype < skip_family_pools)) { - pools.reserve(pools.size()+ctype-skip_family_pools+1); +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::false_type {}; - while(!(ctype < skip_family_pools)) { - pools.emplace(pools.begin()+(skip_family_pools++), pool_data{}); - } - } +/** + * @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>: std::is_invocable {}; - pdata = &pools[ctype]; - } +/** + * @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>: std::is_invocable {}; - if(!pdata->pool) { - pdata->runtime_type = ctype; - pdata->pool = std::make_unique>(); +/** + * @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; - pdata->clone = +[](const sparse_set &other) -> std::unique_ptr> { - if constexpr(std::is_copy_constructible_v>) { - return std::make_unique>(static_cast &>(other)); - } else { - return nullptr; - } - }; +/*! @brief Same as std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::false_type {}; - pdata->remove = +[](basic_registry ®istry, const Entity entt) { - registry.pool()->remove(registry, entt); - }; - } +/** + * @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>: std::is_invocable_r {}; - return static_cast *>(pdata->pool.get()); - } +/** + * @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; -public: - /*! @brief Underlying entity identifier. */ - using entity_type = typename traits_type::entity_type; - /*! @brief Underlying version type. */ - using version_type = typename traits_type::version_type; - /*! @brief Unsigned integer type. */ - using size_type = typename sparse_set::size_type; - /*! @brief Unsigned integer type. */ - using component_type = ENTT_ID_TYPE; +/** + * @brief Provides the member constant `value` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; - /*! @brief Default constructor. */ - basic_registry() ENTT_NOEXCEPT = default; +/*! @copydoc is_complete */ +template +struct is_complete>: std::true_type {}; - /*! @brief Default move constructor. */ - basic_registry(basic_registry &&) = default; +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_complete_v = is_complete::value; - /*! @brief Default move assignment operator. @return This registry. */ - basic_registry & operator=(basic_registry &&) = default; +/** + * @brief Provides the member constant `value` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; - /** - * @brief Returns the numeric identifier of a component. - * - * The given component doesn't need to be necessarily in use.
- * Do not use this functionality to generate numeric identifiers for types - * at runtime. They aren't guaranteed to be stable between different runs. - * - * @tparam Component Type of component to query. - * @return Runtime numeric identifier of the given type of component. - */ - template - inline static component_type type() ENTT_NOEXCEPT { - return runtime_type(); - } +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ - /** - * @brief Returns the number of existing components of the given type. - * @tparam Component Type of component of which to return the size. - * @return Number of existing components of the given type. - */ - template - size_type size() const ENTT_NOEXCEPT { - const auto *cpool = pool(); - return cpool ? cpool->size() : size_type{}; - } +namespace internal { - /** - * @brief Returns the number of entities created so far. - * @return Number of entities created so far. - */ - size_type size() const ENTT_NOEXCEPT { - return entities.size(); - } +template +struct has_iterator_category: std::false_type {}; - /** - * @brief Returns the number of entities still in use. - * @return Number of entities still in use. - */ - size_type alive() const ENTT_NOEXCEPT { - return entities.size() - available; - } +template +struct has_iterator_category::iterator_category>>: std::true_type {}; - /** - * @brief Increases the capacity of the pool for the given component. - * - * If the new capacity is greater than the current capacity, new storage is - * allocated, otherwise the method does nothing. - * - * @tparam Component Type of component for which to reserve storage. - * @param cap Desired capacity. - */ - template - void reserve(const size_type cap) { - assure()->reserve(cap); - } +} // namespace internal - /** - * @brief Increases the capacity of a registry in terms of entities. - * - * 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) { - entities.reserve(cap); - } +/** + * Internal details not to be documented. + * @endcond + */ - /** - * @brief Returns the capacity of the pool for the given component. - * @tparam Component Type of component in which one is interested. - * @return Capacity of the pool of the given component. - */ - template - size_type capacity() const ENTT_NOEXCEPT { - const auto *cpool = pool(); - return cpool ? cpool->capacity() : size_type{}; - } +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : internal::has_iterator_category {}; - /** - * @brief Returns the number of entities that a registry has currently - * allocated space for. - * @return Capacity of the registry. - */ - size_type capacity() const ENTT_NOEXCEPT { - return entities.capacity(); - } +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_iterator_v = is_iterator::value; - /** - * @brief Requests the removal of unused capacity for a given component. - * @tparam Component Type of component for which to reclaim unused capacity. - */ - template - void shrink_to_fit() { - assure()->shrink_to_fit(); - } +/** + * @brief Provides the member constant `value` 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 + : std::conjunction, std::negation>> {}; - /** - * @brief Checks whether the pool of a given component is empty. - * @tparam Component Type of component in which one is interested. - * @return True if the pool of the given component is empty, false - * otherwise. - */ - template - bool empty() const ENTT_NOEXCEPT { - const auto *cpool = pool(); - return cpool ? cpool->empty() : true; - } +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_ebco_eligible_v = is_ebco_eligible::value; - /** - * @brief Checks if there exists at least an entity still in use. - * @return True if at least an entity is still in use, false otherwise. - */ - bool empty() const ENTT_NOEXCEPT { - return entities.size() == available; - } +/** + * @brief Provides the member constant `value` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; - /** - * @brief Direct access to the list of components of a given pool. - * - * The returned pointer is such that range - * `[raw(), raw() + size()]` is always a - * valid range, even if the container is empty. - * - * There are no guarantees on the order of the components. Use a view if you - * want to iterate entities and components in the expected order. - * - * @note - * Empty components aren't explicitly instantiated. Only one instance of the - * given type is created. Therefore, this function always returns a pointer - * to that instance. - * - * @tparam Component Type of component in which one is interested. - * @return A pointer to the array of components of the given type. - */ - template - const Component * raw() const ENTT_NOEXCEPT { - const auto *cpool = pool(); - return cpool ? cpool->raw() : nullptr; - } +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; - /*! @copydoc raw */ - template - inline Component * raw() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).template raw()); - } +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; - /** - * @brief Direct access to the list of entities of a given pool. - * - * The returned pointer is such that range - * `[data(), data() + size()]` is always a - * valid range, even if the container is empty. - * - * There are no guarantees on the order of the entities. Use a view if you - * want to iterate entities and components in the expected order. - * - * @tparam Component Type of component in which one is interested. - * @return A pointer to the array of entities. - */ - template - const entity_type * data() const ENTT_NOEXCEPT { - const auto *cpool = pool(); - return cpool ? cpool->data() : nullptr; - } +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; - /** - * @brief Checks if an entity identifier refers to a valid entity. - * @param entity An entity identifier, either valid or not. - * @return True if the identifier is valid, false otherwise. - */ - bool valid(const entity_type entity) const ENTT_NOEXCEPT { - const auto pos = size_type(entity & traits_type::entity_mask); - return (pos < entities.size() && entities[pos] == entity); - } +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ - /** - * @brief Returns the entity identifier without the version. - * @param entity An entity identifier, either valid or not. - * @return The entity identifier without the version. - */ - static entity_type entity(const entity_type entity) ENTT_NOEXCEPT { - return entity & traits_type::entity_mask; - } +namespace internal { - /** - * @brief Returns the version stored along with an entity identifier. - * @param entity An entity identifier, either valid or not. - * @return The version stored along with the given entity identifier. - */ - static version_type version(const entity_type entity) ENTT_NOEXCEPT { - return version_type(entity >> traits_type::entity_shift); +template +struct has_tuple_size_value: std::false_type {}; + +template +struct has_tuple_size_value::value)>>: std::true_type {}; + +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} + +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} + +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; } +} - /** - * @brief Returns the actual version for an entity identifier. - * - * @warning - * Attempting to use an entity that doesn't belong to the registry results - * in undefined behavior. An entity belongs to the registry even if it has - * been previously destroyed and/or recycled.
- * An assertion will abort the execution at runtime in debug mode if the - * registry doesn't own the given entity. - * - * @param entity A valid entity identifier. - * @return Actual version for the given entity identifier. - */ - version_type current(const entity_type entity) const ENTT_NOEXCEPT { - const auto pos = size_type(entity & traits_type::entity_mask); - ENTT_ASSERT(pos < entities.size()); - return version_type(entities[pos] >> traits_type::entity_shift); +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); } +} - /** - * @brief Creates a new entity and returns it. - * - * There are two kinds of entity identifiers: - * - * * Newly created ones in case no entities have been previously destroyed. - * * Recycled ones with updated versions. - * - * Users should not care about the type of the returned entity identifier. - * In case entity identifers are stored around, the `valid` member - * function can be used to know if they are still valid or the entity has - * been destroyed and potentially recycled. - * - * The returned entity has assigned the given components, if any. The - * components must be at least default constructible. A compilation error - * will occur otherwhise. - * - * @tparam Component Types of components to assign to the entity. - * @return A valid entity identifier if the component list is empty, a tuple - * containing the entity identifier and the references to the components - * just created otherwise. - */ - template - decltype(auto) create() { - entity_type entity; +} // namespace internal - if(available) { - const auto entt = next; - const auto version = entities[entt] & (traits_type::version_mask << traits_type::entity_shift); - next = entities[entt] & traits_type::entity_mask; - entity = entt | version; - entities[entt] = entity; - --available; - } else { - entity = entities.emplace_back(entity_type(entities.size())); - // traits_type::entity_mask is reserved to allow for null identifiers - ENTT_ASSERT(entity < traits_type::entity_mask); - } +/** + * Internal details not to be documented. + * @endcond + */ - if constexpr(sizeof...(Component) == 0) { - return entity; - } else { - return std::tuple(entity))...>{entity, assign(entity)...}; - } - } +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; - /** - * @brief Assigns each element in a range an entity. - * - * @sa create - * - * @tparam Component Types of components to assign to the entity. - * @tparam It Type of forward 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. - * @return No return value if the component list is empty, a tuple - * containing the pointers to the arrays of components just created and - * sorted the same of the entities otherwise. - */ - template - auto create(It first, It last) { - static_assert(std::is_convertible_v::value_type>); - const auto length = size_type(std::distance(first, last)); - const auto sz = std::min(available, length); - [[maybe_unused]] entity_type candidate{}; - - available -= sz; - - const auto tail = std::generate_n(first, sz, [&candidate, this]() mutable { - if constexpr(sizeof...(Component) > 0) { - candidate = std::max(candidate, next); - } else { - // suppress warnings - (void)candidate; - } +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_equality_comparable_v = is_equality_comparable::value; - const auto entt = next; - const auto version = entities[entt] & (traits_type::version_mask << traits_type::entity_shift); - next = entities[entt] & traits_type::entity_mask; - return (entities[entt] = entt | version); - }); +/** + * @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 = std::remove_const_t; +}; - std::generate(tail, last, [this]() { - return entities.emplace_back(entity_type(entities.size())); - }); +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; - if constexpr(sizeof...(Component) > 0) { - return std::make_tuple(assure()->batch(*this, first, last)...); - } - } +/** + * @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 = typename constness_as::type; - /** - * @brief Destroys an entity and lets the registry recycle the identifier. - * - * When an entity is destroyed, its version is updated and the identifier - * can be recycled at any time. In case entity identifers are stored around, - * the `valid` member function can be used to know if they are still valid - * or the entity has been destroyed and potentially recycled. - * - * @warning - * In case there are listeners that observe the destruction of components - * and assign other components to the entity in their bodies, the result of - * invoking this function may not be as expected. In the worst case, it - * could lead to undefined behavior. An assertion will abort the execution - * at runtime in debug mode if a violation is detected. - * - * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. - * - * @param entity A valid entity identifier. - */ - void destroy(const entity_type entity) { - ENTT_ASSERT(valid(entity)); +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); - for(auto pos = pools.size(); pos; --pos) { - if(auto &pdata = pools[pos-1]; pdata.pool && pdata.pool->has(entity)) { - pdata.remove(*this, entity); - } - }; + template + static Class *clazz(Ret (Class::*)(Args...)); - // just a way to protect users from listeners that attach components - ENTT_ASSERT(orphan(entity)); - release(entity); - } + template + static Class *clazz(Ret (Class::*)(Args...) const); - /** - * @brief Destroys all the entities in a range. - * @tparam It Type of forward 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 - void destroy(It first, It last) { - ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); })); + template + static Class *clazz(Type Class::*); - for(auto pos = pools.size(); pos; --pos) { - if(auto &pdata = pools[pos-1]; pdata.pool) { - std::for_each(first, last, [&pdata, this](const auto entity) { - if(pdata.pool->has(entity)) { - pdata.remove(*this, entity); - } - }); - } - }; +public: + /*! @brief The class of the given non-static member object or function. */ + using type = std::remove_pointer_t()))>; +}; - // just a way to protect users from listeners that attach components - ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return orphan(entity); })); +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; - std::for_each(first, last, [this](const auto entity) { - release(entity); - }); - } +} // namespace entt - /** - * @brief Assigns the given component to an entity. - * - * A new instance of the given component is created and initialized with the - * arguments provided (the component must have a proper constructor or be of - * aggregate type). Then the component is assigned to the given entity. - * - * @warning - * Attempting to use an invalid entity or to assign a component to an entity - * that already owns it results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity or if the entity already owns an instance of the given - * component. - * - * @tparam Component Type of component to create. - * @tparam Args Types of arguments to use to construct the component. - * @param entity A valid entity identifier. - * @param args Parameters to use to initialize the component. - * @return A reference to the newly created component. - */ - template - decltype(auto) assign(const entity_type entity, [[maybe_unused]] Args &&... args) { - ENTT_ASSERT(valid(entity)); - return assure()->assign(*this, entity, std::forward(args)...); - } +#endif - /** - * @brief Removes the given component from an entity. - * - * @warning - * Attempting to use an invalid entity or to remove a component from an - * entity that doesn't own it results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity or if the entity doesn't own an instance of the given - * component. - * - * @tparam Component Type of component to remove. - * @param entity A valid entity identifier. - */ - template - void remove(const entity_type entity) { - ENTT_ASSERT(valid(entity)); - pool()->remove(*this, entity); - } +// #include "core/utility.hpp" +#ifndef ENTT_CORE_UTILITY_HPP +#define ENTT_CORE_UTILITY_HPP + +#include +// #include "../config/config.h" + + +namespace entt { + +/*! @brief Identity function object (waiting for C++20). */ +struct identity { + /*! @brief Indicates that this is a transparent function object. */ + using is_transparent = void; /** - * @brief Checks if an entity has all the given components. - * - * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. - * - * @tparam Component Components for which to perform the check. - * @param entity A valid entity identifier. - * @return True if the entity has all the components, false otherwise. + * @brief Returns its argument unchanged. + * @tparam Type Type of the argument. + * @param value The actual argument. + * @return The submitted value as-is. */ - template - bool has(const entity_type entity) const ENTT_NOEXCEPT { - ENTT_ASSERT(valid(entity)); - [[maybe_unused]] const auto cpools = std::make_tuple(pool()...); - return ((std::get *>(cpools) ? std::get *>(cpools)->has(entity) : false) && ...); + template + [[nodiscard]] constexpr Type &&operator()(Type &&value) const ENTT_NOEXCEPT { + return std::forward(value); } +}; - /** - * @brief Returns references to the given components for an entity. - * - * @warning - * Attempting to use an invalid entity or to get a component from an entity - * that doesn't own it results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity or if the entity doesn't own an instance of the given - * component. - * - * @tparam Component Types of components to get. - * @param entity A valid entity identifier. - * @return References to the components owned by the entity. - */ - template - decltype(auto) get([[maybe_unused]] const entity_type entity) const { - ENTT_ASSERT(valid(entity)); +/** + * @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) ENTT_NOEXCEPT { + return member; +} - if constexpr(sizeof...(Component) == 1) { - return (pool()->get(entity), ...); - } else { - return std::tuple(entity))...>{get(entity)...}; - } - } +/** + * @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) ENTT_NOEXCEPT { + return func; +} - /*! @copydoc get */ - template - inline decltype(auto) get([[maybe_unused]] const entity_type entity) ENTT_NOEXCEPT { - ENTT_ASSERT(valid(entity)); +/** + * @brief Helper type for visitors. + * @tparam Func Types of function objects. + */ +template +struct overloaded: Func... { + using Func::operator()...; +}; - if constexpr(sizeof...(Component) == 1) { - return (pool()->get(entity), ...); - } else { - return std::tuple(entity))...>{get(entity)...}; - } - } +/** + * @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 Returns a reference to the given component for an entity. - * - * In case the entity doesn't own the component, the parameters provided are - * used to construct it.
- * Equivalent to the following snippet (pseudocode): - * - * @code{.cpp} - * auto &component = registry.has(entity) ? registry.get(entity) : registry.assign(entity, args...); - * @endcode - * - * Prefer this function anyway because it has slightly better performance. - * - * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. - * - * @tparam Component Type of component to get. - * @tparam Args Types of arguments to use to construct the component. - * @param entity A valid entity identifier. - * @param args Parameters to use to initialize the component. - * @return Reference to the component owned by the entity. + * @brief Constructs a y-combinator from a given function. + * @param recursive A potentially recursive function. */ - template - decltype(auto) get_or_assign(const entity_type entity, Args &&... args) ENTT_NOEXCEPT { - ENTT_ASSERT(valid(entity)); - auto *cpool = assure(); - return cpool->has(entity) ? cpool->get(entity) : cpool->assign(*this, entity, std::forward(args)...); - } + y_combinator(Func recursive) + : func{std::move(recursive)} {} /** - * @brief Returns pointers to the given components for an entity. - * - * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. - * - * @tparam Component Types of components to get. - * @param entity A valid entity identifier. - * @return Pointers to the components owned by the entity. + * @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 - auto try_get([[maybe_unused]] const entity_type entity) const ENTT_NOEXCEPT { - ENTT_ASSERT(valid(entity)); - - if constexpr(sizeof...(Component) == 1) { - const auto cpools = std::make_tuple(pool()...); - return ((std::get *>(cpools) ? std::get *>(cpools)->try_get(entity) : nullptr), ...); - } else { - return std::tuple *...>{try_get(entity)...}; - } + template + decltype(auto) operator()(Args &&...args) const { + return func(*this, std::forward(args)...); } - /*! @copydoc try_get */ - template - inline auto try_get([[maybe_unused]] const entity_type entity) ENTT_NOEXCEPT { - if constexpr(sizeof...(Component) == 1) { - return (const_cast(std::as_const(*this).template try_get(entity)), ...); - } else { - return std::tuple{try_get(entity)...}; - } + /*! @copydoc operator()() */ + template + decltype(auto) operator()(Args &&...args) { + return func(*this, std::forward(args)...); } - /** - * @brief Replaces the given component for an entity. - * - * A new instance of the given component is created and initialized with the - * arguments provided (the component must have a proper constructor or be of - * aggregate type). Then the component is assigned to the given entity. - * - * @warning - * Attempting to use an invalid entity or to replace a component of an - * entity that doesn't own it results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity or if the entity doesn't own an instance of the given - * component. - * - * @tparam Component Type of component to replace. - * @tparam Args Types of arguments to use to construct the component. - * @param entity A valid entity identifier. - * @param args Parameters to use to initialize the component. - * @return A reference to the newly created component. - */ - template - decltype(auto) replace(const entity_type entity, Args &&... args) { - ENTT_ASSERT(valid(entity)); - return pool()->replace(*this, entity, std::forward(args)...); - } +private: + Func func; +}; - /** - * @brief Assigns or replaces the given component for an entity. - * - * Equivalent to the following snippet (pseudocode): - * - * @code{.cpp} - * auto &component = registry.has(entity) ? registry.replace(entity, args...) : registry.assign(entity, args...); +} // namespace entt + +#endif + +// #include "entity/component.hpp" +#ifndef ENTT_ENTITY_COMPONENT_HPP +#define ENTT_ENTITY_COMPONENT_HPP + +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct in_place_delete: std::false_type {}; + +template +struct in_place_delete> + : std::true_type {}; + +template +struct page_size: std::integral_constant) ? 0u : ENTT_PACKED_PAGE> {}; + +template +struct page_size>> + : std::integral_constant {}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Common way to access various properties of components. + * @tparam Type Type of component. + */ +template +struct component_traits { + static_assert(std::is_same_v, Type>, "Unsupported type"); + + /*! @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 std::size_t page_size = internal::page_size::value; +}; + +/** + * @brief Helper variable template. + * @tparam Type Type of component. + */ +template +inline constexpr bool ignore_as_empty_v = (component_traits::page_size == 0u); + +} // namespace entt + +#endif + +// #include "entity/entity.hpp" +#ifndef ENTT_ENTITY_ENTITY_HPP +#define ENTT_ENTITY_ENTITY_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #include "fwd.hpp" +#ifndef ENTT_ENTITY_FWD_HPP +#define ENTT_ENTITY_FWD_HPP + +#include +// #include "../core/fwd.hpp" +#ifndef ENTT_CORE_FWD_HPP +#define ENTT_CORE_FWD_HPP + +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + + +namespace entt { + +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<>; + +} // namespace entt + +#endif + +// #include "utility.hpp" +#ifndef ENTT_ENTITY_UTILITY_HPP +#define ENTT_ENTITY_UTILITY_HPP + +// #include "../core/type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP + +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "fwd.hpp" +#ifndef ENTT_CORE_FWD_HPP +#define ENTT_CORE_FWD_HPP + +#include +#include +// #include "../config/config.h" + + +namespace entt { + +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<>; + +} // namespace entt + +#endif + + +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 TURN_OFF_DOXYGEN */ 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 Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; + +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; + +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; + +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; + +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::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 = std::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 Type 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 Type 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 = Type; +}; + +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; + +/** + * @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 +constexpr 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 = typename 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 = typename type_list_cat::type; + +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; + +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; +}; + +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A type list. + */ +template +using type_list_unique_t = typename type_list_unique::type; + +/** + * @brief Provides the member constant `value` 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>: std::disjunction...> {}; + +/** + * @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 = typename type_list_diff::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 value. */ + static constexpr auto value = Value; +}; + +/** + * @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 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 +constexpr 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 = typename 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 = typename value_list_cat::type; + +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::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>: std::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>: std::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 std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::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>: std::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` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; + +/*! @copydoc is_complete */ +template +struct is_complete>: std::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` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_iterator_category: std::false_type {}; + +template +struct has_iterator_category::iterator_category>>: std::true_type {}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : 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` 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 + : std::conjunction, std::negation>> {}; + +/** + * @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` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; + +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; + +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_tuple_size_value: std::false_type {}; + +template +struct has_tuple_size_value::value)>>: std::true_type {}; + +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} + +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} + +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; + } +} + +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); + } +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; + +/** + * @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 = std::remove_const_t; +}; + +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; + +/** + * @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 = typename constness_as::type; + +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); + + 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 = std::remove_pointer_t()))>; +}; + +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @brief Alias for exclusion lists. + * @tparam Type List of types. + */ +template +struct exclude_t: type_list {}; + +/** + * @brief Variable template for exclusion lists. + * @tparam Type List of types. + */ +template +inline constexpr exclude_t exclude{}; + +/** + * @brief Alias for lists of observed components. + * @tparam Type List of types. + */ +template +struct get_t: type_list {}; + +/** + * @brief Variable template for lists of observed components. + * @tparam Type List of types. + */ +template +inline constexpr get_t get{}; + +/** + * @brief Alias for lists of owned components. + * @tparam Type List of types. + */ +template +struct owned_t: type_list {}; + +/** + * @brief Variable template for lists of owned components. + * @tparam Type List of types. + */ +template +inline constexpr owned_t owned{}; + +} // namespace entt + +#endif + + +namespace entt { + +template> +class basic_sparse_set; + +template, typename = void> +class basic_storage; + +template +class basic_registry; + +template +class basic_view; + +template +struct basic_runtime_view; + +template +class basic_group; + +template +class basic_observer; + +template +class basic_organizer; + +template +struct basic_handle; + +template +class basic_snapshot; + +template +class basic_snapshot_loader; + +template +class basic_continuous_loader; + +/*! @brief Default entity identifier. */ +enum class entity : id_type {}; + +/*! @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 Args Other template parameters. + */ +template +using storage = basic_storage; + +/*! @brief Alias declaration for the most common use case. */ +using registry = basic_registry; + +/*! @brief Alias declaration for the most common use case. */ +using observer = basic_observer; + +/*! @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. + * @tparam Get Types of components iterated by the view. + * @tparam Exclude Types of components used to filter the view. + */ +template> +using view = basic_view; + +/*! @brief Alias declaration for the most common use case. */ +using runtime_view = basic_runtime_view; + +/** + * @brief Alias declaration for the most common use case. + * @tparam Args Other template parameters. + */ +template +using group = basic_group; + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct entt_traits; + +template +struct entt_traits>> + : entt_traits> {}; + +template +struct entt_traits>> + : entt_traits {}; + +template<> +struct entt_traits { + using entity_type = std::uint32_t; + using version_type = std::uint16_t; + + static constexpr entity_type entity_mask = 0xFFFFF; + static constexpr entity_type version_mask = 0xFFF; + static constexpr std::size_t entity_shift = 20u; +}; + +template<> +struct entt_traits { + using entity_type = std::uint64_t; + using version_type = std::uint32_t; + + static constexpr entity_type entity_mask = 0xFFFFFFFF; + static constexpr entity_type version_mask = 0xFFFFFFFF; + static constexpr std::size_t entity_shift = 32u; +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Entity traits. + * @tparam Type Type of identifier. + */ +template +class entt_traits: internal::entt_traits { + using base_type = internal::entt_traits; + +public: + /*! @brief Value type. */ + using value_type = Type; + /*! @brief Underlying entity type. */ + using entity_type = typename base_type::entity_type; + /*! @brief Underlying version type. */ + using version_type = typename base_type::version_type; + /*! @brief Reserved identifier. */ + static constexpr entity_type reserved = base_type::entity_mask | (base_type::version_mask << base_type::entity_shift); + /*! @brief Page size, default is `ENTT_SPARSE_PAGE`. */ + static constexpr auto page_size = ENTT_SPARSE_PAGE; + + /** + * @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) ENTT_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) ENTT_NOEXCEPT { + return (to_integral(value) & base_type::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) ENTT_NOEXCEPT { + return (to_integral(value) >> base_type::entity_shift); + } + + /** + * @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) ENTT_NOEXCEPT { + return value_type{(entity & base_type::entity_mask) | (static_cast(version) << base_type::entity_shift)}; + } + + /** + * @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) ENTT_NOEXCEPT { + constexpr auto mask = (base_type::version_mask << base_type::entity_shift); + return value_type{(lhs & base_type::entity_mask) | (rhs & mask)}; + } +}; + +/** + * @copydoc entt_traits::to_integral + * @tparam Entity The value type. + */ +template +[[nodiscard]] constexpr typename entt_traits::entity_type to_integral(const Entity value) ENTT_NOEXCEPT { + return entt_traits::to_integral(value); +} + +/** + * @copydoc entt_traits::to_entity + * @tparam Entity The value type. + */ +template +[[nodiscard]] constexpr typename entt_traits::entity_type to_entity(const Entity value) ENTT_NOEXCEPT { + return entt_traits::to_entity(value); +} + +/** + * @copydoc entt_traits::to_version + * @tparam Entity The value type. + */ +template +[[nodiscard]] constexpr typename entt_traits::version_type to_version(const Entity value) ENTT_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 ENTT_NOEXCEPT { + using entity_traits = entt_traits; + return entity_traits::combine(entity_traits::reserved, entity_traits::reserved); + } + + /** + * @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 ENTT_NOEXCEPT { + return true; + } + + /** + * @brief Compares two null objects. + * @param other A null object. + * @return False in all cases. + */ + [[nodiscard]] constexpr bool operator!=([[maybe_unused]] const null_t other) const ENTT_NOEXCEPT { + return false; + } + + /** + * @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 ENTT_NOEXCEPT { + using entity_traits = entt_traits; + return entity_traits::to_entity(entity) == entity_traits::to_entity(*this); + } + + /** + * @brief Compares a null object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @return True if the two elements differ, false otherwise. + */ + template + [[nodiscard]] constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT { + return !(entity == *this); + } +}; + +/** + * @brief Compares a null object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @param other A null object yet to be converted. + * @return False if the two elements differ, true otherwise. + */ +template +[[nodiscard]] constexpr bool operator==(const Entity entity, const null_t other) ENTT_NOEXCEPT { + return other.operator==(entity); +} + +/** + * @brief Compares a null object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @param other A null object yet to be converted. + * @return True if the two elements differ, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator!=(const Entity entity, const null_t other) ENTT_NOEXCEPT { + return !(other == entity); +} + +/*! @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 ENTT_NOEXCEPT { + using entity_traits = entt_traits; + return entity_traits::combine(entity_traits::reserved, entity_traits::reserved); + } + + /** + * @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 ENTT_NOEXCEPT { + return true; + } + + /** + * @brief Compares two tombstone objects. + * @param other A tombstone object. + * @return False in all cases. + */ + [[nodiscard]] constexpr bool operator!=([[maybe_unused]] const tombstone_t other) const ENTT_NOEXCEPT { + return false; + } + + /** + * @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 ENTT_NOEXCEPT { + using entity_traits = entt_traits; + return entity_traits::to_version(entity) == entity_traits::to_version(*this); + } + + /** + * @brief Compares a tombstone object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @return True if the two elements differ, false otherwise. + */ + template + [[nodiscard]] constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT { + return !(entity == *this); + } +}; + +/** + * @brief Compares a tombstone object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @param other A tombstone object yet to be converted. + * @return False if the two elements differ, true otherwise. + */ +template +[[nodiscard]] constexpr bool operator==(const Entity entity, const tombstone_t other) ENTT_NOEXCEPT { + return other.operator==(entity); +} + +/** + * @brief Compares a tombstone object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @param other A tombstone object yet to be converted. + * @return True if the two elements differ, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator!=(const Entity entity, const tombstone_t other) ENTT_NOEXCEPT { + return !(other == entity); +} + +/** + * @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 + +// #include "entity/group.hpp" +#ifndef ENTT_ENTITY_GROUP_HPP +#define ENTT_ENTITY_GROUP_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/iterator.hpp" +#ifndef ENTT_CORE_ITERATOR_HPP +#define ENTT_CORE_ITERATOR_HPP + +#include +#include +#include +// #include "../config/config.h" + + +namespace entt { + +/** + * @brief Helper type to use as pointer with input iterators. + * @tparam Type of wrapped value. + */ +template +struct input_iterator_pointer final { + /*! @brief Pointer type. */ + using pointer = Type *; + + /*! @brief Default copy constructor, deleted on purpose. */ + input_iterator_pointer(const input_iterator_pointer &) = delete; + + /*! @brief Default move constructor. */ + input_iterator_pointer(input_iterator_pointer &&) = default; + + /** + * @brief Constructs a proxy object by move. + * @param val Value to use to initialize the proxy object. + */ + input_iterator_pointer(Type &&val) + : value{std::move(val)} {} + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This proxy object. + */ + input_iterator_pointer &operator=(const input_iterator_pointer &) = delete; + + /** + * @brief Default move assignment operator. + * @return This proxy object. + */ + input_iterator_pointer &operator=(input_iterator_pointer &&) = default; + + /** + * @brief Access operator for accessing wrapped values. + * @return A pointer to the wrapped value. + */ + [[nodiscard]] pointer operator->() ENTT_NOEXCEPT { + return std::addressof(value); + } + +private: + Type value; +}; + +/** + * @brief Utility class to create an iterable object from a pair of iterators. + * @tparam It Type of iterator. + * @tparam Sentinel Type of sentinel. + */ +template +struct iterable_adaptor final { + /*! @brief Value type. */ + using value_type = typename std::iterator_traits::value_type; + /*! @brief Iterator type. */ + using iterator = It; + /*! @brief Sentinel type. */ + using sentinel = Sentinel; + + /*! @brief Default constructor. */ + iterable_adaptor() = default; + + /** + * @brief Creates an iterable object from a pair of iterators. + * @param from Begin iterator. + * @param to End iterator. + */ + iterable_adaptor(iterator from, sentinel to) + : first{from}, + last{to} {} + + /** + * @brief Returns an iterator to the beginning. + * @return An iterator to the first element of the range. + */ + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return first; + } + + /** + * @brief Returns an iterator to the end. + * @return An iterator to the element following the last element of the + * range. + */ + [[nodiscard]] sentinel end() const ENTT_NOEXCEPT { + return last; + } + + /*! @copydoc begin */ + [[nodiscard]] iterator cbegin() const ENTT_NOEXCEPT { + return begin(); + } + + /*! @copydoc end */ + [[nodiscard]] sentinel cend() const ENTT_NOEXCEPT { + return end(); + } + +private: + It first; + Sentinel last; +}; + +} // namespace entt + +#endif + +// #include "../core/type_traits.hpp" + +// #include "component.hpp" +#ifndef ENTT_ENTITY_COMPONENT_HPP +#define ENTT_ENTITY_COMPONENT_HPP + +#include +#include +// #include "../config/config.h" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct in_place_delete: std::false_type {}; + +template +struct in_place_delete> + : std::true_type {}; + +template +struct page_size: std::integral_constant) ? 0u : ENTT_PACKED_PAGE> {}; + +template +struct page_size>> + : std::integral_constant {}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Common way to access various properties of components. + * @tparam Type Type of component. + */ +template +struct component_traits { + static_assert(std::is_same_v, Type>, "Unsupported type"); + + /*! @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 std::size_t page_size = internal::page_size::value; +}; + +/** + * @brief Helper variable template. + * @tparam Type Type of component. + */ +template +inline constexpr bool ignore_as_empty_v = (component_traits::page_size == 0u); + +} // namespace entt + +#endif + +// #include "entity.hpp" +#ifndef ENTT_ENTITY_ENTITY_HPP +#define ENTT_ENTITY_ENTITY_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #include "fwd.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct entt_traits; + +template +struct entt_traits>> + : entt_traits> {}; + +template +struct entt_traits>> + : entt_traits {}; + +template<> +struct entt_traits { + using entity_type = std::uint32_t; + using version_type = std::uint16_t; + + static constexpr entity_type entity_mask = 0xFFFFF; + static constexpr entity_type version_mask = 0xFFF; + static constexpr std::size_t entity_shift = 20u; +}; + +template<> +struct entt_traits { + using entity_type = std::uint64_t; + using version_type = std::uint32_t; + + static constexpr entity_type entity_mask = 0xFFFFFFFF; + static constexpr entity_type version_mask = 0xFFFFFFFF; + static constexpr std::size_t entity_shift = 32u; +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Entity traits. + * @tparam Type Type of identifier. + */ +template +class entt_traits: internal::entt_traits { + using base_type = internal::entt_traits; + +public: + /*! @brief Value type. */ + using value_type = Type; + /*! @brief Underlying entity type. */ + using entity_type = typename base_type::entity_type; + /*! @brief Underlying version type. */ + using version_type = typename base_type::version_type; + /*! @brief Reserved identifier. */ + static constexpr entity_type reserved = base_type::entity_mask | (base_type::version_mask << base_type::entity_shift); + /*! @brief Page size, default is `ENTT_SPARSE_PAGE`. */ + static constexpr auto page_size = ENTT_SPARSE_PAGE; + + /** + * @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) ENTT_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) ENTT_NOEXCEPT { + return (to_integral(value) & base_type::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) ENTT_NOEXCEPT { + return (to_integral(value) >> base_type::entity_shift); + } + + /** + * @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) ENTT_NOEXCEPT { + return value_type{(entity & base_type::entity_mask) | (static_cast(version) << base_type::entity_shift)}; + } + + /** + * @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) ENTT_NOEXCEPT { + constexpr auto mask = (base_type::version_mask << base_type::entity_shift); + return value_type{(lhs & base_type::entity_mask) | (rhs & mask)}; + } +}; + +/** + * @copydoc entt_traits::to_integral + * @tparam Entity The value type. + */ +template +[[nodiscard]] constexpr typename entt_traits::entity_type to_integral(const Entity value) ENTT_NOEXCEPT { + return entt_traits::to_integral(value); +} + +/** + * @copydoc entt_traits::to_entity + * @tparam Entity The value type. + */ +template +[[nodiscard]] constexpr typename entt_traits::entity_type to_entity(const Entity value) ENTT_NOEXCEPT { + return entt_traits::to_entity(value); +} + +/** + * @copydoc entt_traits::to_version + * @tparam Entity The value type. + */ +template +[[nodiscard]] constexpr typename entt_traits::version_type to_version(const Entity value) ENTT_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 ENTT_NOEXCEPT { + using entity_traits = entt_traits; + return entity_traits::combine(entity_traits::reserved, entity_traits::reserved); + } + + /** + * @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 ENTT_NOEXCEPT { + return true; + } + + /** + * @brief Compares two null objects. + * @param other A null object. + * @return False in all cases. + */ + [[nodiscard]] constexpr bool operator!=([[maybe_unused]] const null_t other) const ENTT_NOEXCEPT { + return false; + } + + /** + * @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 ENTT_NOEXCEPT { + using entity_traits = entt_traits; + return entity_traits::to_entity(entity) == entity_traits::to_entity(*this); + } + + /** + * @brief Compares a null object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @return True if the two elements differ, false otherwise. + */ + template + [[nodiscard]] constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT { + return !(entity == *this); + } +}; + +/** + * @brief Compares a null object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @param other A null object yet to be converted. + * @return False if the two elements differ, true otherwise. + */ +template +[[nodiscard]] constexpr bool operator==(const Entity entity, const null_t other) ENTT_NOEXCEPT { + return other.operator==(entity); +} + +/** + * @brief Compares a null object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @param other A null object yet to be converted. + * @return True if the two elements differ, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator!=(const Entity entity, const null_t other) ENTT_NOEXCEPT { + return !(other == entity); +} + +/*! @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 ENTT_NOEXCEPT { + using entity_traits = entt_traits; + return entity_traits::combine(entity_traits::reserved, entity_traits::reserved); + } + + /** + * @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 ENTT_NOEXCEPT { + return true; + } + + /** + * @brief Compares two tombstone objects. + * @param other A tombstone object. + * @return False in all cases. + */ + [[nodiscard]] constexpr bool operator!=([[maybe_unused]] const tombstone_t other) const ENTT_NOEXCEPT { + return false; + } + + /** + * @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 ENTT_NOEXCEPT { + using entity_traits = entt_traits; + return entity_traits::to_version(entity) == entity_traits::to_version(*this); + } + + /** + * @brief Compares a tombstone object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @return True if the two elements differ, false otherwise. + */ + template + [[nodiscard]] constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT { + return !(entity == *this); + } +}; + +/** + * @brief Compares a tombstone object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @param other A tombstone object yet to be converted. + * @return False if the two elements differ, true otherwise. + */ +template +[[nodiscard]] constexpr bool operator==(const Entity entity, const tombstone_t other) ENTT_NOEXCEPT { + return other.operator==(entity); +} + +/** + * @brief Compares a tombstone object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @param other A tombstone object yet to be converted. + * @return True if the two elements differ, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator!=(const Entity entity, const tombstone_t other) ENTT_NOEXCEPT { + return !(other == entity); +} + +/** + * @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 + +// #include "fwd.hpp" + +// #include "sparse_set.hpp" +#ifndef ENTT_ENTITY_SPARSE_SET_HPP +#define ENTT_ENTITY_SPARSE_SET_HPP + +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/algorithm.hpp" +#ifndef ENTT_CORE_ALGORITHM_HPP +#define ENTT_CORE_ALGORITHM_HPP + +#include +#include +#include +#include +#include +// #include "utility.hpp" +#ifndef ENTT_CORE_UTILITY_HPP +#define ENTT_CORE_UTILITY_HPP + +#include +// #include "../config/config.h" + + +namespace entt { + +/*! @brief Identity function object (waiting for C++20). */ +struct identity { + /*! @brief Indicates that this is a transparent function object. */ + using is_transparent = void; + + /** + * @brief Returns its argument unchanged. + * @tparam Type Type of the argument. + * @param value The actual argument. + * @return The submitted value as-is. + */ + template + [[nodiscard]] constexpr Type &&operator()(Type &&value) const ENTT_NOEXCEPT { + return std::forward(value); + } +}; + +/** + * @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) ENTT_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) ENTT_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. + */ + y_combinator(Func recursive) + : func{std::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 + decltype(auto) operator()(Args &&...args) const { + return func(*this, std::forward(args)...); + } + + /*! @copydoc operator()() */ + template + decltype(auto) operator()(Args &&...args) { + return func(*this, std::forward(args)...); + } + +private: + Func func; +}; + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @brief Function object to wrap `std::sort` in a class type. + * + * Unfortunately, `std::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 `std::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 It Type of random access iterator. + * @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()(It first, It last, Compare compare = Compare{}, Args &&...args) const { + std::sort(std::forward(args)..., std::move(first), std::move(last), std::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 It Type of random access iterator. + * @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()(It first, It last, Compare compare = Compare{}) const { + if(first < last) { + for(auto it = first + 1; it < last; ++it) { + auto value = std::move(*it); + auto pre = it; + + for(; pre > first && compare(value, *(pre - 1)); --pre) { + *pre = std::move(*(pre - 1)); + } + + *pre = std::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 +struct radix_sort { + static_assert((N % Bit) == 0, "The maximum number of bits to sort must be a multiple of the number of bits processed per pass"); + + /** + * @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) { + static constexpr auto mask = (1 << Bit) - 1; + static constexpr auto buckets = 1 << Bit; + static constexpr auto passes = N / Bit; + + using value_type = typename std::iterator_traits::value_type; + std::vector aux(std::distance(first, last)); + + auto part = [getter = std::move(getter)](auto from, auto to, auto out, auto start) { + std::size_t index[buckets]{}; + std::size_t count[buckets]{}; + + for(auto it = from; it != to; ++it) { + ++count[(getter(*it) >> start) & mask]; + } + + for(std::size_t pos{}, end = buckets - 1u; pos < end; ++pos) { + index[pos + 1u] = index[pos] + count[pos]; + } + + for(auto it = from; it != to; ++it) { + out[index[(getter(*it) >> start) & mask]++] = std::move(*it); + } + }; + + for(std::size_t pass = 0; pass < (passes & ~1); 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); + std::move(aux.begin(), aux.end(), first); + } + } + } +}; + +} // namespace entt + +#endif + +// #include "../core/any.hpp" +#ifndef ENTT_CORE_ANY_HPP +#define ENTT_CORE_ANY_HPP + +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/utility.hpp" +#ifndef ENTT_CORE_UTILITY_HPP +#define ENTT_CORE_UTILITY_HPP + +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + + +namespace entt { + +/*! @brief Identity function object (waiting for C++20). */ +struct identity { + /*! @brief Indicates that this is a transparent function object. */ + using is_transparent = void; + + /** + * @brief Returns its argument unchanged. + * @tparam Type Type of the argument. + * @param value The actual argument. + * @return The submitted value as-is. + */ + template + [[nodiscard]] constexpr Type &&operator()(Type &&value) const ENTT_NOEXCEPT { + return std::forward(value); + } +}; + +/** + * @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) ENTT_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) ENTT_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. + */ + y_combinator(Func recursive) + : func{std::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 + decltype(auto) operator()(Args &&...args) const { + return func(*this, std::forward(args)...); + } + + /*! @copydoc operator()() */ + template + decltype(auto) operator()(Args &&...args) { + return func(*this, std::forward(args)...); + } + +private: + Func func; +}; + +} // namespace entt + +#endif + +// #include "fwd.hpp" + +// #include "type_info.hpp" +#ifndef ENTT_CORE_TYPE_INFO_HPP +#define ENTT_CORE_TYPE_INFO_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/attribute.h" +#ifndef ENTT_CORE_ATTRIBUTE_H +#define ENTT_CORE_ATTRIBUTE_H + +#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 + +#endif + +// #include "fwd.hpp" + +// #include "hashed_string.hpp" +#ifndef ENTT_CORE_HASHED_STRING_HPP +#define ENTT_CORE_HASHED_STRING_HPP + +#include +#include +// #include "../config/config.h" + +// #include "fwd.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct fnv1a_traits; + +template<> +struct fnv1a_traits { + using type = std::uint32_t; + static constexpr std::uint32_t offset = 2166136261; + static constexpr std::uint32_t prime = 16777619; +}; + +template<> +struct fnv1a_traits { + using type = std::uint64_t; + static constexpr std::uint64_t offset = 14695981039346656037ull; + static constexpr std::uint64_t prime = 1099511628211ull; +}; + +template +struct basic_hashed_string { + using value_type = Char; + using size_type = std::size_t; + using hash_type = id_type; + + const value_type *repr; + size_type length; + hash_type hash; +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @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 hs_traits = internal::fnv1a_traits; + + struct const_wrapper { + // non-explicit constructor on purpose + constexpr const_wrapper(const Char *str) ENTT_NOEXCEPT: repr{str} {} + const Char *repr; + }; + + // Fowler–Noll–Vo hash function v. 1a - the good + [[nodiscard]] static constexpr auto helper(const Char *str) ENTT_NOEXCEPT { + base_type base{str, 0u, hs_traits::offset}; + + for(; str[base.length]; ++base.length) { + base.hash = (base.hash ^ static_cast(str[base.length])) * hs_traits::prime; + } + + return base; + } + + // Fowler–Noll–Vo hash function v. 1a - the good + [[nodiscard]] static constexpr auto helper(const Char *str, const std::size_t len) ENTT_NOEXCEPT { + base_type base{str, len, hs_traits::offset}; + + for(size_type pos{}; pos < len; ++pos) { + base.hash = (base.hash ^ static_cast(str[pos])) * hs_traits::prime; + } + + return base; + } + +public: + /*! @brief Character type. */ + using value_type = typename base_type::value_type; + /*! @brief Unsigned integer type. */ + using size_type = typename base_type::size_type; + /*! @brief Unsigned integer type. */ + using hash_type = typename 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) ENTT_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 + [[nodiscard]] static constexpr hash_type value(const value_type (&str)[N]) ENTT_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) ENTT_NOEXCEPT { + return basic_hashed_string{wrapper}; + } + + /*! @brief Constructs an empty hashed string. */ + constexpr basic_hashed_string() ENTT_NOEXCEPT + : base_type{} {} + + /** + * @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) ENTT_NOEXCEPT + : base_type{helper(str, len)} {} + + /** + * @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 + constexpr basic_hashed_string(const value_type (&str)[N]) ENTT_NOEXCEPT + : base_type{helper(str)} {} + + /** + * @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) ENTT_NOEXCEPT + : base_type{helper(wrapper.repr)} {} + + /** + * @brief Returns the size a hashed string. + * @return The size of the hashed string. + */ + [[nodiscard]] constexpr size_type size() const ENTT_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 ENTT_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 ENTT_NOEXCEPT { + return base_type::hash; + } + + /*! @copydoc data */ + [[nodiscard]] constexpr operator const value_type *() const ENTT_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 ENTT_NOEXCEPT { + return 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, const std::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 +basic_hashed_string(const Char (&str)[N]) -> basic_hashed_string; + +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the two hashed strings are identical, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator==(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return lhs.value() == rhs.value(); +} + +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the two hashed strings differ, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator!=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is less than the second, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator<(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return lhs.value() < rhs.value(); +} + +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is less than or equal to the second, false + * otherwise. + */ +template +[[nodiscard]] constexpr bool operator<=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return !(rhs < lhs); +} + +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is greater than the second, false + * otherwise. + */ +template +[[nodiscard]] constexpr bool operator>(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is greater than or equal to the second, + * false otherwise. + */ +template +[[nodiscard]] constexpr bool operator>=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +/*! @brief Aliases for common character types. */ +using hashed_string = basic_hashed_string; + +/*! @brief Aliases for common character types. */ +using hashed_wstring = 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]] constexpr hashed_string operator"" _hs(const char *str, std::size_t) ENTT_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]] constexpr hashed_wstring operator"" _hws(const wchar_t *str, std::size_t) ENTT_NOEXCEPT { + return hashed_wstring{str}; +} + +} // namespace literals + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +struct ENTT_API type_index final { + [[nodiscard]] static id_type next() ENTT_NOEXCEPT { + static ENTT_MAYBE_ATOMIC(id_type) value{}; + return value++; + } +}; + +template +[[nodiscard]] constexpr auto stripped_type_name() ENTT_NOEXCEPT { +#if defined ENTT_PRETTY_FUNCTION + std::string_view pretty_function{ENTT_PRETTY_FUNCTION}; + auto first = pretty_function.find_first_not_of(' ', pretty_function.find_first_of(ENTT_PRETTY_FUNCTION_PREFIX) + 1); + auto value = pretty_function.substr(first, pretty_function.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first); + return value; +#else + return std::string_view{""}; +#endif +} + +template().find_first_of('.')> +[[nodiscard]] static constexpr std::string_view type_name(int) ENTT_NOEXCEPT { + constexpr auto value = stripped_type_name(); + return value; +} + +template +[[nodiscard]] static std::string_view type_name(char) ENTT_NOEXCEPT { + static const auto value = stripped_type_name(); + return value; +} + +template().find_first_of('.')> +[[nodiscard]] static constexpr id_type type_hash(int) ENTT_NOEXCEPT { + constexpr auto stripped = stripped_type_name(); + constexpr auto value = hashed_string::value(stripped.data(), stripped.size()); + return value; +} + +template +[[nodiscard]] static id_type type_hash(char) ENTT_NOEXCEPT { + static const auto value = [](const auto stripped) { + return hashed_string::value(stripped.data(), stripped.size()); + }(stripped_type_name()); + return value; +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @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() ENTT_NOEXCEPT { + static const id_type value = internal::type_index::next(); + return value; + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const ENTT_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() ENTT_NOEXCEPT { + return internal::type_hash(0); +#else + [[nodiscard]] static constexpr id_type value() ENTT_NOEXCEPT { + return type_index::value(); +#endif + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const ENTT_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 std::string_view value() ENTT_NOEXCEPT { + return internal::type_name(0); + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator std::string_view() const ENTT_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 + constexpr type_info(std::in_place_type_t) ENTT_NOEXCEPT + : seq{type_index>>::value()}, + identifier{type_hash>>::value()}, + alias{type_name>>::value()} {} + + /** + * @brief Type index. + * @return Type index. + */ + [[nodiscard]] constexpr id_type index() const ENTT_NOEXCEPT { + return seq; + } + + /** + * @brief Type hash. + * @return Type hash. + */ + [[nodiscard]] constexpr id_type hash() const ENTT_NOEXCEPT { + return identifier; + } + + /** + * @brief Type name. + * @return Type name. + */ + [[nodiscard]] constexpr std::string_view name() const ENTT_NOEXCEPT { + return alias; + } + +private: + id_type seq; + id_type identifier; + std::string_view alias; +}; + +/** + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects are identical, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator==(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return lhs.hash() == rhs.hash(); +} + +/** + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects differ, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator!=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than the second, false otherwise. + */ +[[nodiscard]] constexpr bool operator<(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return lhs.index() < rhs.index(); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than or equal to the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator<=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(rhs < lhs); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator>(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than or equal to the second, + * false otherwise. + */ +[[nodiscard]] constexpr bool operator>=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +/** + * @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() ENTT_NOEXCEPT { + if constexpr(std::is_same_v>>) { + static type_info instance{std::in_place_type}; + return instance; + } else { + return type_id>>(); + } +} + +/*! @copydoc type_id */ +template +[[nodiscard]] const type_info &type_id(Type &&) ENTT_NOEXCEPT { + return type_id>>(); +} + +} // namespace entt + +#endif + +// #include "type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP + +#include +#include +#include +#include +// #include "../config/config.h" + +// #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 TURN_OFF_DOXYGEN */ 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 Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; + +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; + +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; + +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; + +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::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 = std::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 Type 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 Type 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 = Type; +}; + +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; + +/** + * @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 +constexpr 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 = typename 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 = typename type_list_cat::type; + +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; + +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; +}; + +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A type list. + */ +template +using type_list_unique_t = typename type_list_unique::type; + +/** + * @brief Provides the member constant `value` 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>: std::disjunction...> {}; + +/** + * @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 = typename type_list_diff::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 value. */ + static constexpr auto value = Value; +}; + +/** + * @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 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 +constexpr 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 = typename 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 = typename value_list_cat::type; + +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::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>: std::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>: std::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 std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::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>: std::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` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; + +/*! @copydoc is_complete */ +template +struct is_complete>: std::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` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_iterator_category: std::false_type {}; + +template +struct has_iterator_category::iterator_category>>: std::true_type {}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : 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` 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 + : std::conjunction, std::negation>> {}; + +/** + * @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` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; + +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; + +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_tuple_size_value: std::false_type {}; + +template +struct has_tuple_size_value::value)>>: std::true_type {}; + +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} + +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} + +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; + } +} + +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); + } +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; + +/** + * @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 = std::remove_const_t; +}; + +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; + +/** + * @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 = typename constness_as::type; + +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); + + 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 = std::remove_pointer_t()))>; +}; + +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @brief A SBO friendly, type-safe container for single values of any type. + * @tparam Len Size of the storage reserved for the small buffer optimization. + * @tparam Align Optional alignment requirement. + */ +template +class basic_any { + enum class operation : std::uint8_t { + copy, + move, + transfer, + assign, + destroy, + compare, + get + }; + + enum class policy : std::uint8_t { + owner, + ref, + cref + }; + + using storage_type = std::aligned_storage_t; + using vtable_type = const void *(const operation, const basic_any &, const void *); + + template + static constexpr bool in_situ = Len && alignof(Type) <= alignof(storage_type) && sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v; + + template + static const void *basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const basic_any &value, [[maybe_unused]] const void *other) { + static_assert(!std::is_same_v && std::is_same_v>, Type>, "Invalid type"); + const Type *element = nullptr; + + if constexpr(in_situ) { + element = value.owner() ? reinterpret_cast(&value.storage) : static_cast(value.instance); + } else { + element = static_cast(value.instance); + } + + switch(op) { + case operation::copy: + if constexpr(std::is_copy_constructible_v) { + static_cast(const_cast(other))->initialize(*element); + } + break; + case operation::move: + if constexpr(in_situ) { + if(value.owner()) { + return new(&static_cast(const_cast(other))->storage) Type{std::move(*const_cast(element))}; + } + } + + return (static_cast(const_cast(other))->instance = std::exchange(const_cast(value).instance, nullptr)); + case operation::transfer: + if constexpr(std::is_move_assignable_v) { + *const_cast(element) = std::move(*static_cast(const_cast(other))); + return other; + } + [[fallthrough]]; + case operation::assign: + if constexpr(std::is_copy_assignable_v) { + *const_cast(element) = *static_cast(other); + return other; + } + break; + case operation::destroy: + if constexpr(in_situ) { + element->~Type(); + } else if constexpr(std::is_array_v) { + delete[] element; + } else { + delete element; + } + break; + case operation::compare: + if constexpr(!std::is_function_v && !std::is_array_v && is_equality_comparable_v) { + return *static_cast(element) == *static_cast(other) ? other : nullptr; + } else { + return (element == other) ? other : nullptr; + } + case operation::get: + return element; + } + + return nullptr; + } + + template + void initialize([[maybe_unused]] Args &&...args) { + if constexpr(!std::is_void_v) { + info = &type_id>>(); + vtable = basic_vtable>>; + + if constexpr(std::is_lvalue_reference_v) { + static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v && ...), "Invalid arguments"); + mode = std::is_const_v> ? policy::cref : policy::ref; + instance = (std::addressof(args), ...); + } else if constexpr(in_situ) { + if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v) { + new(&storage) Type{std::forward(args)...}; + } else { + new(&storage) Type(std::forward(args)...); + } + } else { + if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v) { + instance = new Type{std::forward(args)...}; + } else { + instance = new Type(std::forward(args)...); + } + } + } + } + + basic_any(const basic_any &other, const policy pol) ENTT_NOEXCEPT + : instance{other.data()}, + info{other.info}, + vtable{other.vtable}, + mode{pol} {} + +public: + /*! @brief Size of the internal storage. */ + static constexpr auto length = Len; + /*! @brief Alignment requirement. */ + static constexpr auto alignment = Align; + + /*! @brief Default constructor. */ + constexpr basic_any() ENTT_NOEXCEPT + : instance{}, + info{&type_id()}, + vtable{}, + mode{policy::owner} {} + + /** + * @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(std::in_place_type_t, Args &&...args) + : basic_any{} { + initialize(std::forward(args)...); + } + + /** + * @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, basic_any>>> + basic_any(Type &&value) + : basic_any{} { + initialize>(std::forward(value)); + } + + /** + * @brief Copy constructor. + * @param other The instance to copy from. + */ + basic_any(const basic_any &other) + : basic_any{} { + if(other.vtable) { + other.vtable(operation::copy, other, this); + } + } + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_any(basic_any &&other) ENTT_NOEXCEPT + : instance{}, + info{other.info}, + vtable{other.vtable}, + mode{other.mode} { + if(other.vtable) { + other.vtable(operation::move, other, this); + } + } + + /*! @brief Frees the internal storage, whatever it means. */ + ~basic_any() { + if(vtable && owner()) { + vtable(operation::destroy, *this, nullptr); + } + } + + /** + * @brief Copy assignment operator. + * @param other The instance to copy from. + * @return This any object. + */ + basic_any &operator=(const basic_any &other) { + reset(); + + if(other.vtable) { + other.vtable(operation::copy, other, this); + } + + return *this; + } + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This any object. + */ + basic_any &operator=(basic_any &&other) ENTT_NOEXCEPT { + reset(); + + if(other.vtable) { + other.vtable(operation::move, other, this); + info = other.info; + vtable = other.vtable; + 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 + std::enable_if_t, basic_any>, basic_any &> + operator=(Type &&value) { + emplace>(std::forward(value)); + return *this; + } + + /** + * @brief Returns the object type if any, `type_id()` otherwise. + * @return The object type if any, `type_id()` otherwise. + */ + [[nodiscard]] const type_info &type() const ENTT_NOEXCEPT { + return *info; + } + + /** + * @brief Returns an opaque pointer to the contained instance. + * @return An opaque pointer the contained instance, if any. + */ + [[nodiscard]] const void *data() const ENTT_NOEXCEPT { + return vtable ? vtable(operation::get, *this, nullptr) : nullptr; + } + + /** + * @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 ENTT_NOEXCEPT { + return *info == req ? data() : nullptr; + } + + /** + * @brief Returns an opaque pointer to the contained instance. + * @return An opaque pointer the contained instance, if any. + */ + [[nodiscard]] void *data() ENTT_NOEXCEPT { + return (!vtable || mode == policy::cref) ? nullptr : const_cast(vtable(operation::get, *this, nullptr)); + } + + /** + * @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) ENTT_NOEXCEPT { + return *info == req ? data() : nullptr; + } + + /** + * @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) { + reset(); + initialize(std::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 any &other) { + if(vtable && mode != policy::cref && *info == *other.info) { + return (vtable(operation::assign, *this, other.data()) != nullptr); + } + + return false; + } + + /*! @copydoc assign */ + bool assign(any &&other) { + if(vtable && mode != policy::cref && *info == *other.info) { + if(auto *val = other.data(); val) { + return (vtable(operation::transfer, *this, val) != nullptr); + } else { + return (vtable(operation::assign, *this, std::as_const(other).data()) != nullptr); + } + } + + return false; + } + + /*! @brief Destroys contained object */ + void reset() { + if(vtable && owner()) { + vtable(operation::destroy, *this, nullptr); + } + + info = &type_id(); + vtable = nullptr; + mode = policy::owner; + } + + /** + * @brief Returns false if a wrapper is empty, true otherwise. + * @return False if the wrapper is empty, true otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return vtable != nullptr; + } + + /** + * @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. + */ + bool operator==(const basic_any &other) const ENTT_NOEXCEPT { + if(vtable && *info == *other.info) { + return (vtable(operation::compare, *this, other.data()) != nullptr); + } + + return (!vtable && !other.vtable); + } + + /** + * @brief Aliasing constructor. + * @return A wrapper that shares a reference to an unmanaged object. + */ + [[nodiscard]] basic_any as_ref() ENTT_NOEXCEPT { + return basic_any{*this, (mode == policy::cref ? policy::cref : policy::ref)}; + } + + /*! @copydoc as_ref */ + [[nodiscard]] basic_any as_ref() const ENTT_NOEXCEPT { + return basic_any{*this, policy::cref}; + } + + /** + * @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 ENTT_NOEXCEPT { + return (mode == policy::owner); + } + +private: + union { + const void *instance; + storage_type storage; + }; + const type_info *info; + vtable_type *vtable; + policy mode; +}; + +/** + * @brief Checks if two wrappers differ in their content. + * @tparam Len Size of the storage reserved for the small buffer optimization. + * @tparam Align Alignment requirement. + * @param lhs A wrapper, either empty or not. + * @param rhs A wrapper, either empty or not. + * @return True if the two wrappers differ in their content, false otherwise. + */ +template +[[nodiscard]] inline bool operator!=(const basic_any &lhs, const basic_any &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Performs type-safe access to the contained object. + * @tparam Type Type to which conversion is required. + * @tparam Len Size of the storage reserved for the small buffer optimization. + * @tparam Align Alignment requirement. + * @param data Target any object. + * @return The element converted to the requested type. + */ +template +Type any_cast(const basic_any &data) ENTT_NOEXCEPT { + const auto *const instance = any_cast>(&data); + ENTT_ASSERT(instance, "Invalid instance"); + return static_cast(*instance); +} + +/*! @copydoc any_cast */ +template +Type any_cast(basic_any &data) ENTT_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 +Type any_cast(basic_any &&data) ENTT_NOEXCEPT { + if constexpr(std::is_copy_constructible_v>>) { + if(auto *const instance = any_cast>(&data); instance) { + return static_cast(std::move(*instance)); + } else { + return any_cast(data); + } + } else { + auto *const instance = any_cast>(&data); + ENTT_ASSERT(instance, "Invalid instance"); + return static_cast(std::move(*instance)); + } +} + +/*! @copydoc any_cast */ +template +const Type *any_cast(const basic_any *data) ENTT_NOEXCEPT { + const auto &info = type_id>>(); + return static_cast(data->data(info)); +} + +/*! @copydoc any_cast */ +template +Type *any_cast(basic_any *data) ENTT_NOEXCEPT { + const auto &info = type_id>>(); + // last attempt to make wrappers for const references return their values + return static_cast(static_cast, Type> *>(data)->data(info)); +} + +/** + * @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 storage 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, std::size_t Align = basic_any::alignment, typename... Args> +basic_any make_any(Args &&...args) { + return basic_any{std::in_place_type, std::forward(args)...}; +} + +/** + * @brief Forwards its argument and avoids copies for lvalue references. + * @tparam Len Size of the storage 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, std::size_t Align = basic_any::alignment, typename Type> +basic_any forward_as_any(Type &&value) { + return basic_any{std::in_place_type, std::decay_t, Type>>, std::forward(value)}; +} + +} // namespace entt + +#endif + +// #include "../core/memory.hpp" +#ifndef ENTT_CORE_MEMORY_HPP +#define ENTT_CORE_MEMORY_HPP + +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" + + +namespace entt { + +/** + * @brief Unwraps fancy pointers, does nothing otherwise (waiting for C++20). + * @tparam Type Pointer type. + * @param ptr Fancy or raw pointer. + * @return A raw pointer that represents the address of the original pointer. + */ +template +[[nodiscard]] constexpr auto to_address(Type &&ptr) ENTT_NOEXCEPT { + if constexpr(std::is_pointer_v>>) { + return ptr; + } else { + return to_address(std::forward(ptr).operator->()); + } +} + +/** + * @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) ENTT_NOEXCEPT { + if constexpr(std::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) ENTT_NOEXCEPT { + if constexpr(std::allocator_traits::propagate_on_container_move_assignment::value) { + lhs = std::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) ENTT_NOEXCEPT { + ENTT_ASSERT(std::allocator_traits::propagate_on_container_swap::value || lhs == rhs, "Cannot swap the containers"); + + if constexpr(std::allocator_traits::propagate_on_container_swap::value) { + using std::swap; + swap(lhs, rhs); + } +} + +/** + * @brief Checks whether a value is a power of two or not. + * @param value A value that may or may not be a power of two. + * @return True if the value is a power of two, false otherwise. + */ +[[nodiscard]] inline constexpr bool is_power_of_two(const std::size_t value) ENTT_NOEXCEPT { + return value && ((value & (value - 1)) == 0); +} + +/** + * @brief Computes the smallest power of two greater than or equal to a value. + * @param value The value to use. + * @return The smallest power of two greater than or equal to the given value. + */ +[[nodiscard]] inline constexpr std::size_t next_power_of_two(const std::size_t value) ENTT_NOEXCEPT { + ENTT_ASSERT(value < (std::size_t{1u} << (std::numeric_limits::digits - 1)), "Numeric limits exceeded"); + std::size_t curr = value - (value != 0u); + + for(int next = 1; next < std::numeric_limits::digits; next = next * 2) { + curr |= curr >> next; + } + + return ++curr; +} + +/** + * @brief Fast module utility function (powers of two only). + * @param value A value for which to calculate the modulus. + * @param mod _Modulus_, it must be a power of two. + * @return The common remainder. + */ +[[nodiscard]] inline constexpr std::size_t fast_mod(const std::size_t value, const std::size_t mod) ENTT_NOEXCEPT { + ENTT_ASSERT(is_power_of_two(mod), "Value must be a power of two"); + return value & (mod - 1u); +} + +/** + * @brief Deleter for allocator-aware unique pointers (waiting for C++20). + * @tparam Args Types of arguments to use to construct the object. + */ +template +struct allocation_deleter: private Allocator { + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Pointer type. */ + using pointer = typename std::allocator_traits::pointer; + + /** + * @brief Inherited constructors. + * @param alloc The allocator to use. + */ + allocation_deleter(const allocator_type &alloc) + : Allocator{alloc} {} + + /** + * @brief Destroys the pointed object and deallocates its memory. + * @param ptr A valid pointer to an object of the given type. + */ + void operator()(pointer ptr) { + using alloc_traits = typename std::allocator_traits; + alloc_traits::destroy(*this, to_address(ptr)); + alloc_traits::deallocate(*this, ptr, 1u); + } +}; + +/** + * @brief Allows `std::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 +auto allocate_unique(Allocator &allocator, Args &&...args) { + static_assert(!std::is_array_v, "Array types are not supported"); + + using alloc_traits = typename std::allocator_traits::template rebind_traits; + using allocator_type = typename alloc_traits::allocator_type; + + allocator_type alloc{allocator}; + auto ptr = alloc_traits::allocate(alloc, 1u); + + ENTT_TRY { + alloc_traits::construct(alloc, to_address(ptr), std::forward(args)...); + } + ENTT_CATCH { + alloc_traits::deallocate(alloc, ptr, 1u); + ENTT_THROW; + } + + return std::unique_ptr>{ptr, alloc}; +} + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct uses_allocator_construction { + template + static constexpr auto args([[maybe_unused]] const Allocator &allocator, Params &&...params) ENTT_NOEXCEPT { + if constexpr(!std::uses_allocator_v && std::is_constructible_v) { + return std::forward_as_tuple(std::forward(params)...); + } else { + static_assert(std::uses_allocator_v, "Ill-formed request"); + + if constexpr(std::is_constructible_v) { + return std::tuple(std::allocator_arg, allocator, std::forward(params)...); + } else { + static_assert(std::is_constructible_v, "Ill-formed request"); + return std::forward_as_tuple(std::forward(params)..., allocator); + } + } + } +}; + +template +struct uses_allocator_construction> { + using type = std::pair; + + template + static constexpr auto args(const Allocator &allocator, std::piecewise_construct_t, First &&first, Second &&second) ENTT_NOEXCEPT { + return std::make_tuple( + std::piecewise_construct, + std::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, std::forward(curr)...); }, std::forward(first)), + std::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, std::forward(curr)...); }, std::forward(second))); + } + + template + static constexpr auto args(const Allocator &allocator) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::tuple<>{}, std::tuple<>{}); + } + + template + static constexpr auto args(const Allocator &allocator, First &&first, Second &&second) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::forward(first)), std::forward_as_tuple(std::forward(second))); + } + + template + static constexpr auto args(const Allocator &allocator, const std::pair &value) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(value.first), std::forward_as_tuple(value.second)); + } + + template + static constexpr auto args(const Allocator &allocator, std::pair &&value) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::move(value.first)), std::forward_as_tuple(std::move(value.second))); + } +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @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 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 The arguments needed to create an object of the given type. + */ +template +constexpr auto uses_allocator_construction_args(const Allocator &allocator, Args &&...args) ENTT_NOEXCEPT { + return internal::uses_allocator_construction::args(allocator, std::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 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 newly created object of the given type. + */ +template +constexpr Type make_obj_using_allocator(const Allocator &allocator, Args &&...args) { + return std::make_from_tuple(internal::uses_allocator_construction::args(allocator, std::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 Allocator Type of allocator used to manage memory and elements. + * @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 Allocator &allocator, Args &&...args) { + return std::apply([&](auto &&...curr) { return new(value) Type(std::forward(curr)...); }, internal::uses_allocator_construction::args(allocator, std::forward(args)...)); +} + +} // namespace entt + +#endif + +// #include "../core/type_info.hpp" +#ifndef ENTT_CORE_TYPE_INFO_HPP +#define ENTT_CORE_TYPE_INFO_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/attribute.h" + +// #include "fwd.hpp" + +// #include "hashed_string.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +struct ENTT_API type_index final { + [[nodiscard]] static id_type next() ENTT_NOEXCEPT { + static ENTT_MAYBE_ATOMIC(id_type) value{}; + return value++; + } +}; + +template +[[nodiscard]] constexpr auto stripped_type_name() ENTT_NOEXCEPT { +#if defined ENTT_PRETTY_FUNCTION + std::string_view pretty_function{ENTT_PRETTY_FUNCTION}; + auto first = pretty_function.find_first_not_of(' ', pretty_function.find_first_of(ENTT_PRETTY_FUNCTION_PREFIX) + 1); + auto value = pretty_function.substr(first, pretty_function.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first); + return value; +#else + return std::string_view{""}; +#endif +} + +template().find_first_of('.')> +[[nodiscard]] static constexpr std::string_view type_name(int) ENTT_NOEXCEPT { + constexpr auto value = stripped_type_name(); + return value; +} + +template +[[nodiscard]] static std::string_view type_name(char) ENTT_NOEXCEPT { + static const auto value = stripped_type_name(); + return value; +} + +template().find_first_of('.')> +[[nodiscard]] static constexpr id_type type_hash(int) ENTT_NOEXCEPT { + constexpr auto stripped = stripped_type_name(); + constexpr auto value = hashed_string::value(stripped.data(), stripped.size()); + return value; +} + +template +[[nodiscard]] static id_type type_hash(char) ENTT_NOEXCEPT { + static const auto value = [](const auto stripped) { + return hashed_string::value(stripped.data(), stripped.size()); + }(stripped_type_name()); + return value; +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @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() ENTT_NOEXCEPT { + static const id_type value = internal::type_index::next(); + return value; + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const ENTT_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() ENTT_NOEXCEPT { + return internal::type_hash(0); +#else + [[nodiscard]] static constexpr id_type value() ENTT_NOEXCEPT { + return type_index::value(); +#endif + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const ENTT_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 std::string_view value() ENTT_NOEXCEPT { + return internal::type_name(0); + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator std::string_view() const ENTT_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 + constexpr type_info(std::in_place_type_t) ENTT_NOEXCEPT + : seq{type_index>>::value()}, + identifier{type_hash>>::value()}, + alias{type_name>>::value()} {} + + /** + * @brief Type index. + * @return Type index. + */ + [[nodiscard]] constexpr id_type index() const ENTT_NOEXCEPT { + return seq; + } + + /** + * @brief Type hash. + * @return Type hash. + */ + [[nodiscard]] constexpr id_type hash() const ENTT_NOEXCEPT { + return identifier; + } + + /** + * @brief Type name. + * @return Type name. + */ + [[nodiscard]] constexpr std::string_view name() const ENTT_NOEXCEPT { + return alias; + } + +private: + id_type seq; + id_type identifier; + std::string_view alias; +}; + +/** + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects are identical, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator==(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return lhs.hash() == rhs.hash(); +} + +/** + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects differ, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator!=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than the second, false otherwise. + */ +[[nodiscard]] constexpr bool operator<(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return lhs.index() < rhs.index(); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than or equal to the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator<=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(rhs < lhs); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator>(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than or equal to the second, + * false otherwise. + */ +[[nodiscard]] constexpr bool operator>=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +/** + * @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() ENTT_NOEXCEPT { + if constexpr(std::is_same_v>>) { + static type_info instance{std::in_place_type}; + return instance; + } else { + return type_id>>(); + } +} + +/*! @copydoc type_id */ +template +[[nodiscard]] const type_info &type_id(Type &&) ENTT_NOEXCEPT { + return type_id>>(); +} + +} // namespace entt + +#endif + +// #include "entity.hpp" + +// #include "fwd.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct sparse_set_iterator final { + using value_type = typename Container::value_type; + using pointer = typename Container::const_pointer; + using reference = typename Container::const_reference; + using difference_type = typename Container::difference_type; + using iterator_category = std::random_access_iterator_tag; + + sparse_set_iterator() ENTT_NOEXCEPT + : packed{}, + offset{} {} + + sparse_set_iterator(const Container &ref, const difference_type idx) ENTT_NOEXCEPT + : packed{std::addressof(ref)}, + offset{idx} {} + + sparse_set_iterator &operator++() ENTT_NOEXCEPT { + return --offset, *this; + } + + sparse_set_iterator operator++(int) ENTT_NOEXCEPT { + sparse_set_iterator orig = *this; + return ++(*this), orig; + } + + sparse_set_iterator &operator--() ENTT_NOEXCEPT { + return ++offset, *this; + } + + sparse_set_iterator operator--(int) ENTT_NOEXCEPT { + sparse_set_iterator orig = *this; + return operator--(), orig; + } + + sparse_set_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + offset -= value; + return *this; + } + + sparse_set_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + sparse_set_iterator copy = *this; + return (copy += value); + } + + sparse_set_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); + } + + sparse_set_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); + } + + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + return packed->data()[index() - value]; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return packed->data() + index(); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return *operator->(); + } + + [[nodiscard]] difference_type index() const ENTT_NOEXCEPT { + return offset - 1; + } + +private: + const Container *packed; + difference_type offset; +}; + +template +[[nodiscard]] std::ptrdiff_t operator-(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return rhs.index() - lhs.index(); +} + +template +[[nodiscard]] bool operator==(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() == rhs.index(); +} + +template +[[nodiscard]] bool operator!=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +[[nodiscard]] bool operator<(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() > rhs.index(); +} + +template +[[nodiscard]] bool operator>(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() < rhs.index(); +} + +template +[[nodiscard]] bool operator<=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +template +[[nodiscard]] bool operator>=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @brief Sparse set deletion policy. */ +enum class deletion_policy : std::uint8_t { + /*! @brief Swap-and-pop deletion policy. */ + swap_and_pop = 0u, + /*! @brief In-place deletion policy. */ + in_place = 1u +}; + +/** + * @brief Basic 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 is largely used by the registry to offer users the fastest access ever + * to the components. Views and groups in general are almost entirely designed + * around sparse sets. + * + * 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 (see entt_traits for more details). + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +class basic_sparse_set { + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using sparse_container_type = std::vector>; + using packed_container_type = std::vector; + using entity_traits = entt_traits; + + [[nodiscard]] auto sparse_ptr(const Entity entt) const { + const auto pos = static_cast(entity_traits::to_entity(entt)); + const auto page = pos / entity_traits::page_size; + return (page < sparse.size() && sparse[page]) ? (sparse[page] + fast_mod(pos, entity_traits::page_size)) : nullptr; + } + + [[nodiscard]] auto &sparse_ref(const Entity entt) const { + ENTT_ASSERT(sparse_ptr(entt), "Invalid element"); + const auto pos = static_cast(entity_traits::to_entity(entt)); + return sparse[pos / entity_traits::page_size][fast_mod(pos, entity_traits::page_size)]; + } + + [[nodiscard]] auto &assure_at_least(const Entity entt) { + const auto pos = static_cast(entity_traits::to_entity(entt)); + const auto page = pos / entity_traits::page_size; + + if(!(page < sparse.size())) { + sparse.resize(page + 1u, nullptr); + } + + if(!sparse[page]) { + auto page_allocator{packed.get_allocator()}; + sparse[page] = alloc_traits::allocate(page_allocator, entity_traits::page_size); + std::uninitialized_fill(sparse[page], sparse[page] + entity_traits::page_size, null); + } + + auto &elem = sparse[page][fast_mod(pos, entity_traits::page_size)]; + ENTT_ASSERT(entity_traits::to_version(elem) == entity_traits::to_version(tombstone), "Slot not available"); + return elem; + } + + void release_sparse_pages() { + auto page_allocator{packed.get_allocator()}; + + for(auto &&page: sparse) { + if(page != nullptr) { + std::destroy(page, page + entity_traits::page_size); + alloc_traits::deallocate(page_allocator, page, entity_traits::page_size); + page = nullptr; + } + } + } + +private: + virtual const void *get_at(const std::size_t) const { + return nullptr; + } + + virtual void swap_at(const std::size_t, const std::size_t) {} + virtual void move_element(const std::size_t, const std::size_t) {} + +protected: + /*! @brief Random access iterator type. */ + using basic_iterator = internal::sparse_set_iterator; + + /** + * @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 swap_and_pop(basic_iterator first, basic_iterator last) { + for(; first != last; ++first) { + auto &self = sparse_ref(*first); + const auto entt = entity_traits::to_entity(self); + sparse_ref(packed.back()) = entity_traits::combine(entt, entity_traits::to_integral(packed.back())); + packed[static_cast(entt)] = packed.back(); + // unnecessary but it helps to detect nasty bugs + ENTT_ASSERT((packed.back() = tombstone, true), ""); + // lazy self-assignment guard + self = null; + packed.pop_back(); + } + } + + /** + * @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 in_place_pop(basic_iterator first, basic_iterator last) { + for(; first != last; ++first) { + const auto entt = entity_traits::to_entity(std::exchange(sparse_ref(*first), null)); + packed[static_cast(entt)] = std::exchange(free_list, entity_traits::combine(entt, entity_traits::reserved)); + } + } + + /** + * @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(!contains(entt), "Set already contains entity"); + + if(auto &elem = assure_at_least(entt); free_list == null || force_back) { + packed.push_back(entt); + elem = entity_traits::combine(static_cast(packed.size() - 1u), entity_traits::to_integral(entt)); + return begin(); + } else { + const auto pos = static_cast(entity_traits::to_entity(free_list)); + elem = entity_traits::combine(entity_traits::to_integral(free_list), entity_traits::to_integral(entt)); + free_list = std::exchange(packed[pos], entt); + return --(end() - pos); + } + } + +public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Underlying version type. */ + using version_type = typename entity_traits::version_type; + /*! @brief Unsigned integer type. */ + using size_type = typename packed_container_type::size_type; + /*! @brief Pointer type to contained entities. */ + using pointer = typename 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 = std::reverse_iterator; + /*! @brief Constant reverse iterator type. */ + using const_reverse_iterator = 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{type_id(), 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 value 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 &value, deletion_policy pol = deletion_policy::swap_and_pop, const allocator_type &allocator = {}) + : sparse{allocator}, + packed{allocator}, + info{&value}, + free_list{tombstone}, + mode{pol} {} + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_sparse_set(basic_sparse_set &&other) ENTT_NOEXCEPT + : sparse{std::move(other.sparse)}, + packed{std::move(other.packed)}, + info{other.info}, + free_list{std::exchange(other.free_list, tombstone)}, + mode{other.mode} {} + + /** + * @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) ENTT_NOEXCEPT + : sparse{std::move(other.sparse), allocator}, + packed{std::move(other.packed), allocator}, + info{other.info}, + free_list{std::exchange(other.free_list, tombstone)}, + mode{other.mode} { + ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.get_allocator() == other.packed.get_allocator(), "Copying a sparse set is not allowed"); + } + + /*! @brief Default destructor. */ + virtual ~basic_sparse_set() { + release_sparse_pages(); + } + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This sparse set. + */ + basic_sparse_set &operator=(basic_sparse_set &&other) ENTT_NOEXCEPT { + ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.get_allocator() == other.packed.get_allocator(), "Copying a sparse set is not allowed"); + + release_sparse_pages(); + sparse = std::move(other.sparse); + packed = std::move(other.packed); + info = other.info; + free_list = std::exchange(other.free_list, tombstone); + mode = other.mode; + 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) { + using std::swap; + swap(sparse, other.sparse); + swap(packed, other.packed); + swap(info, other.info); + swap(free_list, other.free_list); + swap(mode, other.mode); + } + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_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 ENTT_NOEXCEPT { + return mode; + } + + /** + * @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 ENTT_NOEXCEPT { + return packed.capacity(); + } + + /*! @brief Requests the removal of unused capacity. */ + virtual void 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 the internal packed 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 Extent of the sparse set. + */ + [[nodiscard]] size_type extent() const ENTT_NOEXCEPT { + return sparse.size() * entity_traits::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 ENTT_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 ENTT_NOEXCEPT { + return packed.empty(); + } + + /** + * @brief Direct access to the internal packed array. + * @return A pointer to the internal packed array. + */ + [[nodiscard]] pointer data() const ENTT_NOEXCEPT { + return packed.data(); + } + + /** + * @brief Returns an iterator to the beginning. + * + * The returned iterator points to the first entity of the internal packed + * array. 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]] const_iterator begin() const ENTT_NOEXCEPT { + const auto pos = static_cast(packed.size()); + return iterator{packed, pos}; + } + + /*! @copydoc begin */ + [[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT { + return begin(); + } + + /** + * @brief Returns an iterator to the end. + * + * The returned iterator points to the element following the last entity in + * a sparse set. Attempting to dereference the returned iterator results in + * undefined behavior. + * + * @return An iterator to the element following the last entity of a sparse + * set. + */ + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return iterator{packed, {}}; + } + + /*! @copydoc end */ + [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { + return end(); + } + + /** + * @brief Returns a reverse iterator to the beginning. + * + * The returned iterator points to the first entity of the reversed internal + * packed array. 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]] const_reverse_iterator rbegin() const ENTT_NOEXCEPT { + return std::make_reverse_iterator(end()); + } + + /*! @copydoc rbegin */ + [[nodiscard]] const_reverse_iterator crbegin() const ENTT_NOEXCEPT { + return rbegin(); + } + + /** + * @brief Returns a reverse iterator to the end. + * + * The returned iterator points to the element following the last entity in + * the reversed sparse set. Attempting to dereference the returned iterator + * results in undefined behavior. + * + * @return An iterator to the element following the last entity of the + * reversed sparse set. + */ + [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT { + return std::make_reverse_iterator(begin()); + } + + /*! @copydoc rend */ + [[nodiscard]] const_reverse_iterator crend() const ENTT_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]] iterator find(const entity_type entt) const ENTT_NOEXCEPT { + return contains(entt) ? --(end() - index(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 ENTT_NOEXCEPT { + const auto elem = sparse_ptr(entt); + constexpr auto cap = entity_traits::to_entity(null); + // testing versions permits to avoid accessing the packed array + return elem && (((~cap & entity_traits::to_integral(entt)) ^ entity_traits::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 ENTT_NOEXCEPT { + const auto elem = sparse_ptr(entt); + constexpr auto fallback = entity_traits::to_version(tombstone); + return elem ? entity_traits::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 ENTT_NOEXCEPT { + ENTT_ASSERT(contains(entt), "Set does not contain entity"); + return static_cast(entity_traits::to_entity(sparse_ref(entt))); + } + + /** + * @brief Returns the entity at specified location, with bounds checking. + * @param pos The position for which to return the entity. + * @return The entity at specified location if any, a null entity otherwise. + */ + [[nodiscard]] entity_type at(const size_type pos) const ENTT_NOEXCEPT { + return pos < packed.size() ? packed[pos] : null; + } + + /** + * @brief Returns the entity at specified location, without bounds checking. + * @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 ENTT_NOEXCEPT { + ENTT_ASSERT(pos < packed.size(), "Position is 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. + */ + const void *get(const entity_type entt) const ENTT_NOEXCEPT { + return get_at(index(entt)); + } + + /*! @copydoc get */ + void *get(const entity_type entt) ENTT_NOEXCEPT { + return const_cast(std::as_const(*this).get(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 value Optional opaque value to forward to mixins, if any. + * @return Iterator pointing to the emplaced element in case of success, the + * `end()` iterator otherwise. + */ + iterator emplace(const entity_type entt, const void *value = nullptr) { + return try_emplace(entt, false, value); + } + + /** + * @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. + */ + void bump(const entity_type entt) { + auto &entity = sparse_ref(entt); + entity = entity_traits::combine(entity_traits::to_integral(entity), entity_traits::to_integral(entt)); + packed[static_cast(entity_traits::to_entity(entity))] = entt; + } + + /** + * @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. + * + * @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 Iterator pointing to the first element inserted in case of + * success, the `end()` iterator otherwise. + */ + template + iterator insert(It first, It last) { + for(auto it = first; it != last; ++it) { + try_emplace(*it, true); + } + + return first == last ? end() : find(*first); + } + + /** + * @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 = --(end() - index(entt)); + (mode == deletion_policy::in_place) ? in_place_pop(it, it + 1u) : swap_and_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(std::is_same_v) { + (mode == deletion_policy::in_place) ? in_place_pop(first, last) : swap_and_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{}; + + for(; first != last; ++first) { + count += remove(*first); + } + + return count; + } + + /*! @brief Removes all tombstones from the packed array of a sparse set. */ + void compact() { + size_type from = packed.size(); + for(; from && packed[from - 1u] == tombstone; --from) {} + + for(auto *it = &free_list; *it != null && from; it = std::addressof(packed[entity_traits::to_entity(*it)])) { + if(const size_type to = entity_traits::to_entity(*it); to < from) { + --from; + move_element(from, to); + + using std::swap; + swap(packed[from], packed[to]); + + const auto entity = static_cast(to); + sparse_ref(packed[to]) = entity_traits::combine(entity, entity_traits::to_integral(packed[to])); + *it = entity_traits::combine(static_cast(from), entity_traits::reserved); + for(; from && packed[from - 1u] == tombstone; --from) {} + } + } + + free_list = tombstone; + packed.resize(from); + } + + /** + * @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) { + ENTT_ASSERT(contains(lhs) && contains(rhs), "Set does not contain entities"); + + auto &entt = sparse_ref(lhs); + auto &other = sparse_ref(rhs); + + const auto from = entity_traits::to_entity(entt); + const auto to = entity_traits::to_entity(other); + + // basic no-leak guarantee (with invalid state) if swapping throws + swap_at(static_cast(from), static_cast(to)); + entt = entity_traits::combine(to, entity_traits::to_integral(packed[from])); + other = entity_traits::combine(from, entity_traits::to_integral(packed[to])); + + using std::swap; + swap(packed[from], packed[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(!(length > packed.size()), "Length exceeds the number of elements"); + ENTT_ASSERT(free_list == null, "Partial sorting with tombstones is not supported"); + + algo(packed.rend() - length, packed.rend(), std::move(compare), std::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_at(next, idx); + const auto entity = static_cast(curr); + sparse_ref(entt) = entity_traits::combine(entity, entity_traits::to_integral(packed[curr])); + curr = std::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) { + compact(); + sort_n(packed.size(), std::move(compare), std::move(algo), std::forward(args)...); + } + + /** + * @brief Sort entities according to their order in another sparse set. + * + * Entities that are part of both the sparse sets are ordered internally + * according to the order they have in `other`. All the other entities goes + * to the end of the list and there are no guarantees on their order.
+ * In other terms, this function can be used to impose the same order on two + * sets by using one of them as a master and the other one as a slave. + * + * Iterating the sparse set with a couple of iterators returns elements in + * the expected order after a call to `respect`. See `begin` and `end` for + * more details. + * + * @param other The sparse sets that imposes the order of the entities. + */ + void respect(const basic_sparse_set &other) { + compact(); + + const auto to = other.end(); + auto from = other.begin(); + + for(size_type pos = packed.size() - 1; pos && from != to; ++from) { + if(contains(*from)) { + if(*from != packed[pos]) { + // basic no-leak guarantee (with invalid state) if swapping throws + swap_elements(packed[pos], *from); + } + + --pos; + } + } + } + + /*! @brief Clears a sparse set. */ + void clear() { + if(const auto last = end(); free_list == null) { + in_place_pop(begin(), last); + } else { + for(auto &&entity: *this) { + // tombstone filter on itself + if(const auto it = find(entity); it != last) { + in_place_pop(it, it + 1u); + } + } + } + + free_list = tombstone; + packed.clear(); + } + + /** + * @brief Returned value type, if any. + * @return Returned value type, if any. + */ + const type_info &type() const ENTT_NOEXCEPT { + return *info; + } + + /*! @brief Forwards variables to mixins, if any. */ + virtual void bind(any) ENTT_NOEXCEPT {} + +private: + sparse_container_type sparse; + packed_container_type packed; + const type_info *info; + entity_type free_list; + deletion_policy mode; +}; + +} // namespace entt + +#endif + +// #include "storage.hpp" +#ifndef ENTT_ENTITY_STORAGE_HPP +#define ENTT_ENTITY_STORAGE_HPP + +#include +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/compressed_pair.hpp" +#ifndef ENTT_CORE_COMPRESSED_PAIR_HPP +#define ENTT_CORE_COMPRESSED_PAIR_HPP + +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "type_traits.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct compressed_pair_element { + using reference = Type &; + using const_reference = const Type &; + + template>> + compressed_pair_element() + : value{} {} + + template>, compressed_pair_element>>> + compressed_pair_element(Args &&args) + : value{std::forward(args)} {} + + template + compressed_pair_element(std::tuple args, std::index_sequence) + : value{std::forward(std::get(args))...} {} + + [[nodiscard]] reference get() ENTT_NOEXCEPT { + return value; + } + + [[nodiscard]] const_reference get() const ENTT_NOEXCEPT { + return value; + } + +private: + Type value; +}; + +template +struct compressed_pair_element>>: Type { + using reference = Type &; + using const_reference = const Type &; + using base_type = Type; + + template>> + compressed_pair_element() + : base_type{} {} + + template>, compressed_pair_element>>> + compressed_pair_element(Args &&args) + : base_type{std::forward(args)} {} + + template + compressed_pair_element(std::tuple args, std::index_sequence) + : base_type{std::forward(std::get(args))...} {} + + [[nodiscard]] reference get() ENTT_NOEXCEPT { + return *this; + } + + [[nodiscard]] const_reference get() const ENTT_NOEXCEPT { + return *this; + } +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @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. + * + * @tparam Dummy Dummy template parameter used for internal purposes. + */ + template && std::is_default_constructible_v>> + constexpr compressed_pair() + : 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) = 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) + : first_base{std::forward(arg)}, + second_base{std::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(std::piecewise_construct_t, std::tuple args, std::tuple other) + : first_base{std::move(args), std::index_sequence_for{}}, + second_base{std::move(other), std::index_sequence_for{}} {} + + /** + * @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) = default; + + /** + * @brief Returns the first element that a pair stores. + * @return The first element that a pair stores. + */ + [[nodiscard]] first_type &first() ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /*! @copydoc first */ + [[nodiscard]] const first_type &first() const ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /** + * @brief Returns the second element that a pair stores. + * @return The second element that a pair stores. + */ + [[nodiscard]] second_type &second() ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /*! @copydoc second */ + [[nodiscard]] const second_type &second() const ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /** + * @brief Swaps two compressed pair objects. + * @param other The compressed pair to swap with. + */ + void swap(compressed_pair &other) { + using std::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 + decltype(auto) get() ENTT_NOEXCEPT { + if constexpr(Index == 0u) { + return first(); + } else { + static_assert(Index == 1u, "Index out of bounds"); + return second(); + } + } + + /*! @copydoc get */ + template + decltype(auto) get() const ENTT_NOEXCEPT { + if constexpr(Index == 0u) { + return first(); + } else { + static_assert(Index == 1u, "Index out of bounds"); + 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, std::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 +inline void swap(compressed_pair &lhs, compressed_pair &rhs) { + lhs.swap(rhs); +} + +} // namespace entt + +// disable structured binding support for clang 6, it messes when specializing tuple_size +#if !defined __clang_major__ || __clang_major__ > 6 +namespace std { + +/** + * @brief `std::tuple_size` specialization for `compressed_pair`s. + * @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 +struct tuple_size>: integral_constant {}; + +/** + * @brief `std::tuple_element` specialization for `compressed_pair`s. + * @tparam Index The index of the type to return. + * @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 +struct tuple_element>: conditional { + static_assert(Index < 2u, "Index out of bounds"); +}; + +} // namespace std +#endif + +#endif + +// #include "../core/iterator.hpp" + +// #include "../core/memory.hpp" + +// #include "../core/type_info.hpp" + +// #include "component.hpp" + +// #include "entity.hpp" + +// #include "fwd.hpp" + +// #include "sigh_storage_mixin.hpp" +#ifndef ENTT_ENTITY_SIGH_STORAGE_MIXIN_HPP +#define ENTT_ENTITY_SIGH_STORAGE_MIXIN_HPP + +#include +// #include "../config/config.h" + +// #include "../core/any.hpp" + +// #include "../signal/sigh.hpp" +#ifndef ENTT_SIGNAL_SIGH_HPP +#define ENTT_SIGNAL_SIGH_HPP + +#include +#include +#include +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "delegate.hpp" +#ifndef ENTT_SIGNAL_DELEGATE_HPP +#define ENTT_SIGNAL_DELEGATE_HPP + +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP + +#include +#include +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "fwd.hpp" +#ifndef ENTT_CORE_FWD_HPP +#define ENTT_CORE_FWD_HPP + +#include +#include +// #include "../config/config.h" + + +namespace entt { + +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<>; + +} // namespace entt + +#endif + + +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 TURN_OFF_DOXYGEN */ 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 Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; + +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; + +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; + +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; + +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::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 = std::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 Type 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 Type 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 = Type; +}; + +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; + +/** + * @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 +constexpr 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 = typename 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 = typename type_list_cat::type; + +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; + +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; +}; + +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A type list. + */ +template +using type_list_unique_t = typename type_list_unique::type; + +/** + * @brief Provides the member constant `value` 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>: std::disjunction...> {}; + +/** + * @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 = typename type_list_diff::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 value. */ + static constexpr auto value = Value; +}; + +/** + * @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 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 +constexpr 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 = typename 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 = typename value_list_cat::type; + +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::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>: std::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>: std::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 std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::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>: std::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` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; + +/*! @copydoc is_complete */ +template +struct is_complete>: std::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` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_iterator_category: std::false_type {}; + +template +struct has_iterator_category::iterator_category>>: std::true_type {}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : 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` 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 + : std::conjunction, std::negation>> {}; + +/** + * @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` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; + +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; + +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_tuple_size_value: std::false_type {}; + +template +struct has_tuple_size_value::value)>>: std::true_type {}; + +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} + +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} + +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; + } +} + +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); + } +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; + +/** + * @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 = std::remove_const_t; +}; + +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; + +/** + * @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 = typename constness_as::type; + +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); + + 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 = std::remove_pointer_t()))>; +}; + +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; + +} // namespace entt + +#endif + +// #include "fwd.hpp" +#ifndef ENTT_SIGNAL_FWD_HPP +#define ENTT_SIGNAL_FWD_HPP + +#include + +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<>; + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +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 +auto function_pointer(Type Class::*, Other &&...) -> Type (*)(); + +template +using function_pointer_t = decltype(internal::function_pointer(std::declval()...)); + +template +[[nodiscard]] constexpr auto index_sequence_for(Ret (*)(Args...)) { + return std::index_sequence_for{}; +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @brief Used to wrap a function or a member of a specified type. */ +template +struct connect_arg_t {}; + +/*! @brief Constant of type connect_arg_t used to disambiguate calls. */ +template +inline constexpr connect_arg_t connect_arg{}; + +/** + * @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 { + template + [[nodiscard]] auto wrap(std::index_sequence) ENTT_NOEXCEPT { + return [](const void *, Args... args) -> Ret { + [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); + return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); + }; + } + + template + [[nodiscard]] auto wrap(Type &, std::index_sequence) ENTT_NOEXCEPT { + return [](const void *payload, Args... args) -> Ret { + [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); + Type *curr = static_cast(const_cast *>(payload)); + return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); + }; + } + + template + [[nodiscard]] auto wrap(Type *, std::index_sequence) ENTT_NOEXCEPT { + return [](const void *payload, Args... args) -> Ret { + [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); + Type *curr = static_cast(const_cast *>(payload)); + return static_cast(std::invoke(Candidate, curr, std::forward>>(std::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() ENTT_NOEXCEPT + : instance{nullptr}, + fn{nullptr} {} + + /** + * @brief Constructs a delegate and connects a free function or an unbound + * member. + * @tparam Candidate Function or member to connect to the delegate. + */ + template + delegate(connect_arg_t) ENTT_NOEXCEPT { + connect(); + } + + /** + * @brief Constructs a delegate and connects a free function with payload or + * a bound member. + * @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 object that fits the purpose. + */ + template + delegate(connect_arg_t, Type &&value_or_instance) ENTT_NOEXCEPT { + connect(std::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) ENTT_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() ENTT_NOEXCEPT { + instance = nullptr; + + if constexpr(std::is_invocable_r_v) { + fn = [](const void *, Args... args) -> Ret { + return Ret(std::invoke(Candidate, std::forward(args)...)); + }; + } else if constexpr(std::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) ENTT_NOEXCEPT { + instance = &value_or_instance; + + if constexpr(std::is_invocable_r_v) { + fn = [](const void *payload, Args... args) -> Ret { + Type *curr = static_cast(const_cast *>(payload)); + return Ret(std::invoke(Candidate, *curr, std::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) ENTT_NOEXCEPT { + instance = value_or_instance; + + if constexpr(std::is_invocable_r_v) { + fn = [](const void *payload, Args... args) -> Ret { + Type *curr = static_cast(const_cast *>(payload)); + return Ret(std::invoke(Candidate, curr, std::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) ENTT_NOEXCEPT { + instance = payload; + fn = function; + } + + /** + * @brief Resets a delegate. + * + * After a reset, a delegate cannot be invoked anymore. + */ + void reset() ENTT_NOEXCEPT { + instance = nullptr; + fn = nullptr; + } + + /** + * @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 ENTT_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, std::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 ENTT_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 ENTT_NOEXCEPT { + return fn == other.fn && instance == other.instance; + } + +private: + const void *instance; + function_type *fn; +}; + +/** + * @brief Compares the contents of two delegates. + * @tparam Ret Return type of a function type. + * @tparam Args Types of arguments of a function type. + * @param lhs A valid delegate object. + * @param rhs A valid delegate object. + * @return True if the two contents differ, false otherwise. + */ +template +[[nodiscard]] bool operator!=(const delegate &lhs, const delegate &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @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 + +// #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 { + /*! @brief A sink is allowed to modify a signal. */ + friend class sink>; + + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using container_type = std::vector, typename alloc_traits::template rebind_alloc>>; + +public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Sink type. */ + using sink_type = sink>; + + /*! @brief Default constructor. */ + sigh() + : sigh{allocator_type{}} {} + + /** + * @brief Constructs a signal handler with a given allocator. + * @param allocator The allocator to use. + */ + explicit sigh(const allocator_type &allocator) + : 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) ENTT_NOEXCEPT + : calls{std::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) ENTT_NOEXCEPT + : calls{std::move(other.calls), allocator} {} + + /** + * @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) ENTT_NOEXCEPT { + calls = std::move(other.calls); + 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) { + using std::swap; + swap(calls, other.calls); + } + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return calls.get_allocator(); + } + + /** + * @brief Instance type when it comes to connecting member functions. + * @tparam Class Type of class to which the member function belongs. + */ + template + using instance_type = Class *; + + /** + * @brief Number of listeners connected to the signal. + * @return Number of listeners currently connected. + */ + [[nodiscard]] size_type size() const ENTT_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 ENTT_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 &&call: std::as_const(calls)) { + call(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 &&call: calls) { + if constexpr(std::is_void_v) { + if constexpr(std::is_invocable_r_v) { + call(args...); + if(func()) { break; } + } else { + call(args...); + func(); + } + } else { + if constexpr(std::is_invocable_r_v) { + if(func(call(args...))) { break; } + } else { + func(call(args...)); + } + } + } + } + +private: + container_type calls; +}; + +/** + * @brief Connection class. + * + * Opaque object the aim of which is to allow users to release an already + * estabilished connection without having to keep a reference to the signal or + * the sink that generated it. + */ +class connection { + /*! @brief A sink is allowed to create connection objects. */ + template + friend class sink; + + connection(delegate fn, void *ref) + : disconnect{fn}, signal{ref} {} + +public: + /*! @brief Default constructor. */ + connection() = default; + + /** + * @brief Checks whether a connection is properly initialized. + * @return True if the connection is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_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 + * estabilished 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) ENTT_NOEXCEPT + : conn{std::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) ENTT_NOEXCEPT { + conn = std::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 = std::move(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 ENTT_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 difference_type = typename 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(); + } + +public: + /** + * @brief Constructs a sink that is allowed to modify a given signal. + * @param ref A valid reference to a signal object. + */ + sink(sigh &ref) ENTT_NOEXCEPT + : offset{}, + 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 ENTT_NOEXCEPT { + return signal->calls.empty(); + } + + /** + * @brief Returns a sink that connects before a given free function or an + * unbound member. + * @tparam Function A valid free function pointer. + * @return A properly initialized sink object. + */ + template + [[nodiscard]] sink before() { + delegate call{}; + call.template connect(); + + const auto &calls = signal->calls; + const auto it = std::find(calls.cbegin(), calls.cend(), std::move(call)); + + sink other{*this}; + other.offset = calls.cend() - it; + return other; + } + + /** + * @brief Returns a sink that connects before a free function with payload + * or a bound member. + * @tparam Candidate Member or free function to look for. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid object that fits the purpose. + * @return A properly initialized sink object. + */ + template + [[nodiscard]] sink before(Type &&value_or_instance) { + delegate call{}; + call.template connect(value_or_instance); + + const auto &calls = signal->calls; + const auto it = std::find(calls.cbegin(), calls.cend(), std::move(call)); + + sink other{*this}; + other.offset = calls.cend() - it; + return other; + } + + /** + * @brief Returns a sink that connects before a given instance or specific + * payload. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid object that fits the purpose. + * @return A properly initialized sink object. + */ + template + [[nodiscard]] sink before(Type &value_or_instance) { + return before(&value_or_instance); + } + + /** + * @brief Returns a sink that connects before a given instance or specific + * payload. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid pointer that fits the purpose. + * @return A properly initialized sink object. + */ + template + [[nodiscard]] sink before(Type *value_or_instance) { + sink other{*this}; + + if(value_or_instance) { + const auto &calls = signal->calls; + const auto it = std::find_if(calls.cbegin(), calls.cend(), [value_or_instance](const auto &delegate) { + return delegate.data() == value_or_instance; + }); + + other.offset = calls.cend() - it; + } + + return other; + } + + /** + * @brief Returns a sink that connects before anything else. + * @return A properly initialized sink object. + */ + [[nodiscard]] sink before() { + sink other{*this}; + other.offset = signal->calls.size(); + return other; + } + + /** + * @brief Connects a free function or an unbound member to a signal. + * + * The signal handler performs checks to avoid multiple connections for the + * same function. + * + * @tparam Candidate Function or member to connect to the signal. + * @return A properly initialized connection object. + */ + template + connection connect() { + disconnect(); + + delegate call{}; + call.template connect(); + signal->calls.insert(signal->calls.end() - offset, std::move(call)); + + delegate conn{}; + conn.template connect<&release>(); + return {std::move(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. On the other side, the signal handler performs + * checks to avoid multiple connections for the same function.
+ * 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 object that fits the purpose. + * @return A properly initialized connection object. + */ + template + connection connect(Type &&value_or_instance) { + disconnect(value_or_instance); + + delegate call{}; + call.template connect(value_or_instance); + signal->calls.insert(signal->calls.end() - offset, std::move(call)); + + delegate conn{}; + conn.template connect<&release>(value_or_instance); + return {std::move(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() { + auto &calls = signal->calls; + delegate call{}; + call.template connect(); + calls.erase(std::remove(calls.begin(), calls.end(), std::move(call)), calls.end()); + } + + /** + * @brief Disconnects a free function with payload or a bound member from a + * signal. + * @tparam Candidate Function or member to disconnect from the signal. + * @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) { + auto &calls = signal->calls; + delegate call{}; + call.template connect(value_or_instance); + calls.erase(std::remove(calls.begin(), calls.end(), std::move(call)), calls.end()); + } + + /** + * @brief Disconnects free functions with payload or bound members from a + * signal. + * @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 Disconnects free functions with payload or bound members from a + * signal. + * @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) { + if(value_or_instance) { + auto &calls = signal->calls; + auto predicate = [value_or_instance](const auto &delegate) { return delegate.data() == value_or_instance; }; + calls.erase(std::remove_if(calls.begin(), calls.end(), std::move(predicate)), calls.end()); + } + } + + /*! @brief Disconnects all the listeners from a signal. */ + void disconnect() { + signal->calls.clear(); + } + +private: + difference_type offset; + 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 + +// #include "fwd.hpp" + + +namespace entt { + +/** + * @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 The type of the underlying storage. + */ +template +class sigh_storage_mixin final: public Type { + using basic_iterator = typename Type::basic_iterator; + + template + void notify_destruction(basic_iterator first, basic_iterator last, Func func) { + ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); + + for(; first != last; ++first) { + const auto entt = *first; + destruction.publish(*owner, entt); + const auto it = Type::find(entt); + func(it, it + 1u); + } + } + + void swap_and_pop(basic_iterator first, basic_iterator last) final { + notify_destruction(std::move(first), std::move(last), [this](auto... args) { Type::swap_and_pop(args...); }); + } + + void in_place_pop(basic_iterator first, basic_iterator last) final { + notify_destruction(std::move(first), std::move(last), [this](auto... args) { Type::in_place_pop(args...); }); + } + + basic_iterator try_emplace(const typename Type::entity_type entt, const bool force_back, const void *value) final { + ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); + Type::try_emplace(entt, force_back, value); + construction.publish(*owner, entt); + return Type::find(entt); + } + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = typename Type::entity_type; + + /*! @brief Inherited constructors. */ + using Type::Type; + + /** + * @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() ENTT_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() ENTT_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() ENTT_NOEXCEPT { + return sink{destruction}; + } + + /** + * @brief Assigns entities to a storage. + * @tparam Args Types of arguments to use to construct the object. + * @param entt A valid identifier. + * @param args Parameters to use to initialize the object. + * @return A reference to the newly created object. + */ + template + decltype(auto) emplace(const entity_type entt, Args &&...args) { + ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); + Type::emplace(entt, std::forward(args)...); + construction.publish(*owner, entt); + return this->get(entt); + } + + /** + * @brief Patches the given instance for an entity. + * @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) { + ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); + Type::patch(entt, std::forward(func)...); + update.publish(*owner, entt); + return this->get(entt); + } + + /** + * @brief Assigns entities to a storage. + * @tparam It Type of input iterator. + * @tparam Args Types of arguments to use to construct the objects assigned + * to the entities. + * @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 initialize the objects assigned to the + * entities. + */ + template + void insert(It first, It last, Args &&...args) { + ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); + Type::insert(first, last, std::forward(args)...); + + for(auto it = construction.empty() ? last : first; it != last; ++it) { + construction.publish(*owner, *it); + } + } + + /** + * @brief Forwards variables to mixins, if any. + * @param value A variable wrapped in an opaque container. + */ + void bind(any value) ENTT_NOEXCEPT final { + auto *reg = any_cast>(&value); + owner = reg ? reg : owner; + Type::bind(std::move(value)); + } + +private: + sigh &, const entity_type)> construction{}; + sigh &, const entity_type)> destruction{}; + sigh &, const entity_type)> update{}; + basic_registry *owner{}; +}; + +} // namespace entt + +#endif + +// #include "sparse_set.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +class storage_iterator final { + friend storage_iterator; + + using container_type = std::remove_const_t; + using alloc_traits = std::allocator_traits; + using comp_traits = component_traits; + + using iterator_traits = std::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 = typename iterator_traits::value_type; + using pointer = typename iterator_traits::pointer; + using reference = typename iterator_traits::reference; + using difference_type = typename iterator_traits::difference_type; + using iterator_category = std::random_access_iterator_tag; + + storage_iterator() ENTT_NOEXCEPT = default; + + storage_iterator(Container *ref, difference_type idx) ENTT_NOEXCEPT + : packed{ref}, + offset{idx} {} + + template, typename = std::enable_if_t> + storage_iterator(const storage_iterator> &other) ENTT_NOEXCEPT + : packed{other.packed}, + offset{other.offset} {} + + storage_iterator &operator++() ENTT_NOEXCEPT { + return --offset, *this; + } + + storage_iterator operator++(int) ENTT_NOEXCEPT { + storage_iterator orig = *this; + return ++(*this), orig; + } + + storage_iterator &operator--() ENTT_NOEXCEPT { + return ++offset, *this; + } + + storage_iterator operator--(int) ENTT_NOEXCEPT { + storage_iterator orig = *this; + return operator--(), orig; + } + + storage_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + offset -= value; + return *this; + } + + storage_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + storage_iterator copy = *this; + return (copy += value); + } + + storage_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); + } + + storage_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); + } + + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + const auto pos = index() - value; + return (*packed)[pos / comp_traits::page_size][fast_mod(pos, comp_traits::page_size)]; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + const auto pos = index(); + return (*packed)[pos / comp_traits::page_size] + fast_mod(pos, comp_traits::page_size); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return *operator->(); + } + + [[nodiscard]] difference_type index() const ENTT_NOEXCEPT { + return offset - 1; + } + +private: + Container *packed; + difference_type offset; +}; + +template +[[nodiscard]] std::ptrdiff_t operator-(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return rhs.index() - lhs.index(); +} + +template +[[nodiscard]] bool operator==(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() == rhs.index(); +} + +template +[[nodiscard]] bool operator!=(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +[[nodiscard]] bool operator<(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() > rhs.index(); +} + +template +[[nodiscard]] bool operator>(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() < rhs.index(); +} + +template +[[nodiscard]] bool operator<=(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +template +[[nodiscard]] bool operator>=(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +template +class extended_storage_iterator final { + template + friend class extended_storage_iterator; + +public: + using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval()), std::forward_as_tuple(*std::declval()...))); + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + extended_storage_iterator() = default; + + extended_storage_iterator(It base, Other... other) + : it{base, other...} {} + + template && ...) && (std::is_constructible_v && ...)>> + extended_storage_iterator(const extended_storage_iterator &other) + : it{other.it} {} + + extended_storage_iterator &operator++() ENTT_NOEXCEPT { + return ++std::get(it), (++std::get(it), ...), *this; + } + + extended_storage_iterator operator++(int) ENTT_NOEXCEPT { + extended_storage_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return {*std::get(it), *std::get(it)...}; + } + + template + friend bool operator==(const extended_storage_iterator &, const extended_storage_iterator &) ENTT_NOEXCEPT; + +private: + std::tuple it; +}; + +template +[[nodiscard]] bool operator==(const extended_storage_iterator &lhs, const extended_storage_iterator &rhs) ENTT_NOEXCEPT { + return std::get<0>(lhs.it) == std::get<0>(rhs.it); +} + +template +[[nodiscard]] bool operator!=(const extended_storage_iterator &lhs, const extended_storage_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Basic 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 Entity A valid entity type (see entt_traits for more details). + * @tparam Type Type of objects assigned to the entities. + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +class basic_storage: public basic_sparse_set::template rebind_alloc> { + static_assert(std::is_move_constructible_v && std::is_move_assignable_v, "The type must be at least move constructible/assignable"); + + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using underlying_type = basic_sparse_set>; + using container_type = std::vector>; + using comp_traits = component_traits; + + [[nodiscard]] auto &element_at(const std::size_t pos) const { + return packed.first()[pos / comp_traits::page_size][fast_mod(pos, comp_traits::page_size)]; + } + + auto assure_at_least(const std::size_t pos) { + auto &&container = packed.first(); + const auto idx = pos / comp_traits::page_size; + + if(!(idx < container.size())) { + auto curr = container.size(); + container.resize(idx + 1u, nullptr); + + ENTT_TRY { + for(const auto last = container.size(); curr < last; ++curr) { + container[curr] = alloc_traits::allocate(packed.second(), comp_traits::page_size); + } + } + ENTT_CATCH { + container.resize(curr); + ENTT_THROW; + } + } + + return container[idx] + fast_mod(pos, comp_traits::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 = assure_at_least(static_cast(it.index())); + entt::uninitialized_construct_using_allocator(to_address(elem), packed.second(), std::forward(args)...); + } + ENTT_CATCH { + if constexpr(comp_traits::in_place_delete) { + base_type::in_place_pop(it, it + 1u); + } else { + base_type::swap_and_pop(it, it + 1u); + } + + ENTT_THROW; + } + + return it; + } + + void shrink_to_size(const std::size_t sz) { + for(auto pos = sz, length = base_type::size(); pos < length; ++pos) { + if constexpr(comp_traits::in_place_delete) { + if(base_type::at(pos) != tombstone) { + std::destroy_at(std::addressof(element_at(pos))); + } + } else { + std::destroy_at(std::addressof(element_at(pos))); + } + } + + auto &&container = packed.first(); + auto page_allocator{packed.second()}; + const auto from = (sz + comp_traits::page_size - 1u) / comp_traits::page_size; + + for(auto pos = from, last = container.size(); pos < last; ++pos) { + alloc_traits::deallocate(page_allocator, container[pos], comp_traits::page_size); + } + + container.resize(from); + } + +private: + const void *get_at(const std::size_t pos) const final { + return std::addressof(element_at(pos)); + } + + void swap_at(const std::size_t lhs, const std::size_t rhs) final { + using std::swap; + swap(element_at(lhs), element_at(rhs)); + } + + void move_element(const std::size_t from, const std::size_t to) final { + auto &elem = element_at(from); + entt::uninitialized_construct_using_allocator(to_address(assure_at_least(to)), packed.second(), std::move(elem)); + std::destroy_at(std::addressof(elem)); + } + +protected: + /** + * @brief Erases elements from a storage. + * @param first An iterator to the first element to erase. + * @param last An iterator past the last element to erase. + */ + void swap_and_pop(typename underlying_type::basic_iterator first, typename underlying_type::basic_iterator last) override { + for(; first != last; ++first) { + // cannot use first::index() because it would break with cross iterators + const auto pos = base_type::index(*first); + auto &elem = element_at(base_type::size() - 1u); + // destroying on exit allows reentrant destructors + [[maybe_unused]] auto unused = std::exchange(element_at(pos), std::move(elem)); + std::destroy_at(std::addressof(elem)); + base_type::swap_and_pop(first, first + 1u); + } + } + + /** + * @brief Erases elements from a storage. + * @param first An iterator to the first element to erase. + * @param last An iterator past the last element to erase. + */ + void in_place_pop(typename underlying_type::basic_iterator first, typename underlying_type::basic_iterator last) override { + for(; first != last; ++first) { + // cannot use first::index() because it would break with cross iterators + const auto pos = base_type::index(*first); + base_type::in_place_pop(first, first + 1u); + std::destroy_at(std::addressof(element_at(pos))); + } + } + + /** + * @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. + */ + typename underlying_type::basic_iterator try_emplace([[maybe_unused]] const Entity entt, const bool force_back, const void *value) override { + if(value) { + if constexpr(std::is_copy_constructible_v) { + return emplace_element(entt, force_back, *static_cast(value)); + } else { + return base_type::end(); + } + } else { + if constexpr(std::is_default_constructible_v) { + return emplace_element(entt, force_back); + } else { + return base_type::end(); + } + } + } + +public: + /*! @brief Base type. */ + using base_type = underlying_type; + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Type of the objects assigned to entities. */ + using value_type = Type; + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Pointer type to contained elements. */ + using pointer = typename container_type::pointer; + /*! @brief Constant pointer type to contained elements. */ + using const_pointer = typename 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 = std::reverse_iterator; + /*! @brief Constant reverse iterator type. */ + using const_reverse_iterator = std::reverse_iterator; + /*! @brief Extended iterable storage proxy. */ + using iterable = iterable_adaptor>; + /*! @brief Constant extended iterable storage proxy. */ + using const_iterable = iterable_adaptor>; + + /*! @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(), deletion_policy{comp_traits::in_place_delete}, allocator}, + packed{container_type{allocator}, allocator} {} + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_storage(basic_storage &&other) ENTT_NOEXCEPT + : base_type{std::move(other)}, + packed{std::move(other.packed)} {} + + /** + * @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) ENTT_NOEXCEPT + : base_type{std::move(other), allocator}, + packed{container_type{std::move(other.packed.first()), allocator}, allocator} { + ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.second() == other.packed.second(), "Copying a storage is not allowed"); + } + + /*! @brief Default destructor. */ + ~basic_storage() override { + shrink_to_size(0u); + } + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This storage. + */ + basic_storage &operator=(basic_storage &&other) ENTT_NOEXCEPT { + ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.second() == other.packed.second(), "Copying a storage is not allowed"); + + shrink_to_size(0u); + base_type::operator=(std::move(other)); + packed.first() = std::move(other.packed.first()); + propagate_on_container_move_assignment(packed.second(), other.packed.second()); + 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) { + using std::swap; + underlying_type::swap(other); + propagate_on_container_swap(packed.second(), other.packed.second()); + swap(packed.first(), other.packed.first()); + } + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return allocator_type{packed.second()}; + } + + /** + * @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 ENTT_NOEXCEPT override { + return packed.first().size() * comp_traits::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 ENTT_NOEXCEPT { + return packed.first().data(); + } + + /*! @copydoc raw */ + [[nodiscard]] pointer raw() ENTT_NOEXCEPT { + return packed.first().data(); + } + + /** + * @brief Returns an iterator to the beginning. + * + * The returned iterator points to the first instance of the internal array. + * 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 ENTT_NOEXCEPT { + const auto pos = static_cast(base_type::size()); + return const_iterator{&packed.first(), pos}; + } + + /*! @copydoc cbegin */ + [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT { + return cbegin(); + } + + /*! @copydoc begin */ + [[nodiscard]] iterator begin() ENTT_NOEXCEPT { + const auto pos = static_cast(base_type::size()); + return iterator{&packed.first(), pos}; + } + + /** + * @brief Returns an iterator to the end. + * + * The returned iterator points to the element following the last instance + * of the internal array. Attempting to dereference the returned iterator + * results in undefined behavior. + * + * @return An iterator to the element following the last instance of the + * internal array. + */ + [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { + return const_iterator{&packed.first(), {}}; + } + + /*! @copydoc cend */ + [[nodiscard]] const_iterator end() const ENTT_NOEXCEPT { + return cend(); + } + + /*! @copydoc end */ + [[nodiscard]] iterator end() ENTT_NOEXCEPT { + return iterator{&packed.first(), {}}; + } + + /** + * @brief Returns a reverse iterator to the beginning. + * + * The returned iterator points to the first instance of the reversed + * internal array. 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 ENTT_NOEXCEPT { + return std::make_reverse_iterator(cend()); + } + + /*! @copydoc crbegin */ + [[nodiscard]] const_reverse_iterator rbegin() const ENTT_NOEXCEPT { + return crbegin(); + } + + /*! @copydoc rbegin */ + [[nodiscard]] reverse_iterator rbegin() ENTT_NOEXCEPT { + return std::make_reverse_iterator(end()); + } + + /** + * @brief Returns a reverse iterator to the end. + * + * The returned iterator points to the element following the last instance + * of the reversed internal array. Attempting to dereference the returned + * iterator results in undefined behavior. + * + * @return An iterator to the element following the last instance of the + * reversed internal array. + */ + [[nodiscard]] const_reverse_iterator crend() const ENTT_NOEXCEPT { + return std::make_reverse_iterator(cbegin()); + } + + /*! @copydoc crend */ + [[nodiscard]] const_reverse_iterator rend() const ENTT_NOEXCEPT { + return crend(); + } + + /*! @copydoc rend */ + [[nodiscard]] reverse_iterator rend() ENTT_NOEXCEPT { + return std::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 ENTT_NOEXCEPT { + return element_at(base_type::index(entt)); + } + + /*! @copydoc get */ + [[nodiscard]] value_type &get(const entity_type entt) ENTT_NOEXCEPT { + return const_cast(std::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]] std::tuple get_as_tuple(const entity_type entt) const ENTT_NOEXCEPT { + return std::forward_as_tuple(get(entt)); + } + + /*! @copydoc get_as_tuple */ + [[nodiscard]] std::tuple get_as_tuple(const entity_type entt) ENTT_NOEXCEPT { + return std::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(std::is_aggregate_v) { + const auto it = emplace_element(entt, false, Type{std::forward(args)...}); + return element_at(static_cast(it.index())); + } else { + const auto it = emplace_element(entt, false, std::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); + (std::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. + * + * @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 value An instance of the object to construct. + */ + template + void insert(It first, It last, const value_type &value = {}) { + for(; first != last; ++first) { + emplace_element(*first, true, value); + } + } + + /** + * @brief Assigns one or more entities to a storage and constructs their + * objects from a given range. + * + * @sa construct + * + * @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 objects. + */ + template::value_type, value_type>>> + void insert(EIt first, EIt last, CIt from) { + for(; first != last; ++first, ++from) { + emplace_element(*first, true, *from); + } + } + + /** + * @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 component. + * + * @return An iterable object to use to _visit_ the storage. + */ + [[nodiscard]] iterable each() ENTT_NOEXCEPT { + return {internal::extended_storage_iterator{base_type::begin(), begin()}, internal::extended_storage_iterator{base_type::end(), end()}}; + } + + /*! @copydoc each */ + [[nodiscard]] const_iterable each() const ENTT_NOEXCEPT { + return {internal::extended_storage_iterator{base_type::cbegin(), cbegin()}, internal::extended_storage_iterator{base_type::cend(), cend()}}; + } + +private: + compressed_pair packed; +}; + +/*! @copydoc basic_storage */ +template +class basic_storage>> + : public basic_sparse_set::template rebind_alloc> { + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using underlying_type = basic_sparse_set>; + using comp_traits = component_traits; + +public: + /*! @brief Base type. */ + using base_type = underlying_type; + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Type of the objects assigned to entities. */ + using value_type = Type; + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Extended iterable storage proxy. */ + using iterable = iterable_adaptor>; + /*! @brief Constant extended iterable storage proxy. */ + using const_iterable = iterable_adaptor>; + + /*! @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(), deletion_policy{comp_traits::in_place_delete}, allocator} {} + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_storage(basic_storage &&other) ENTT_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) ENTT_NOEXCEPT + : base_type{std::move(other), allocator} {} + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This storage. + */ + basic_storage &operator=(basic_storage &&other) ENTT_NOEXCEPT = default; + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_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 ENTT_NOEXCEPT { + ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity"); + } + + /** + * @brief Returns an empty tuple. + * + * @warning + * Attempting to use an entity that doesn't belong to the storage results in + * undefined behavior. + * + * @param entt A valid identifier. + * @return Returns an empty tuple. + */ + [[nodiscard]] std::tuple<> get_as_tuple([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { + ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity"); + return std::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. + * + * @tparam Args Types of arguments to use to construct the object. + * @param entt A valid identifier. + */ + template + void emplace(const entity_type entt, Args &&...) { + 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), "Storage does not contain entity"); + (std::forward(func)(), ...); + } + + /** + * @brief Assigns entities to a storage. + * @tparam It Type of input iterator. + * @tparam Args Types of optional arguments. + * @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 insert(It first, It last, Args &&...) { + 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() ENTT_NOEXCEPT { + return {internal::extended_storage_iterator{base_type::begin()}, internal::extended_storage_iterator{base_type::end()}}; + } + + /*! @copydoc each */ + [[nodiscard]] const_iterable each() const ENTT_NOEXCEPT { + return {internal::extended_storage_iterator{base_type::cbegin()}, internal::extended_storage_iterator{base_type::cend()}}; + } +}; + +/** + * @brief Provides a common way to access certain properties of storage types. + * @tparam Entity A valid entity type (see entt_traits for more details). + * @tparam Type Type of objects managed by the storage class. + */ +template +struct storage_traits { + /*! @brief Resulting type after component-to-storage conversion. */ + using storage_type = sigh_storage_mixin>; +}; + +} // namespace entt + +#endif + +// #include "utility.hpp" + + +namespace entt { + +/** + * @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 have at + * least the given components. Moreover, it's guaranteed that the entity list + * is tightly packed in memory for fast iterations. + * + * @b Important + * + * Iterators aren't invalidated if: + * + * * New instances of the given components are created and assigned to entities. + * * The entity currently pointed is modified (as an example, if one of the + * given components is removed from the entity to which the iterator points). + * * The entity currently pointed is destroyed. + * + * In all other cases, modifying the pools iterated by the group in any way + * invalidates all the iterators and using them results in undefined behavior. + * + * @note + * Groups share references to the underlying data structures of the registry + * that generated them. Therefore any change to the entities and to the + * components made by means of the registry are immediately reflected by all the + * groups.
+ * Moreover, sorting a non-owning group affects all the instances of the same + * group (it means that users don't have to call `sort` on each instance to sort + * all of them because they _share_ entities and components). + * + * @warning + * Lifetime of a group must not overcome that of the registry that generated it. + * In any other case, attempting to use a group results in undefined behavior. + * + * @tparam Entity A valid entity type (see entt_traits for more details). + * @tparam Get Type of components observed by the group. + * @tparam Exclude Types of components used to filter the group. + */ +template +class basic_group, get_t, exclude_t> { + /*! @brief A registry is allowed to create groups. */ + friend class basic_registry; + + template + using storage_type = constness_as_t>::storage_type, Comp>; + + using basic_common_type = std::common_type_t::base_type...>; + + struct extended_group_iterator final { + using difference_type = std::ptrdiff_t; + using value_type = decltype(std::tuple_cat(std::tuple{}, std::declval().get({}))); + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + extended_group_iterator() = default; + + extended_group_iterator(typename basic_common_type::iterator from, const std::tuple *...> &args) + : it{from}, + pools{args} {} + + extended_group_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } + + extended_group_iterator operator++(int) ENTT_NOEXCEPT { + extended_group_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + const auto entt = *it; + return std::tuple_cat(std::make_tuple(entt), std::get *>(pools)->get_as_tuple(entt)...); + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + [[nodiscard]] bool operator==(const extended_group_iterator &other) const ENTT_NOEXCEPT { + return other.it == it; + } + + [[nodiscard]] bool operator!=(const extended_group_iterator &other) const ENTT_NOEXCEPT { + return !(*this == other); + } + + private: + typename basic_common_type::iterator it; + std::tuple *...> pools; + }; + + basic_group(basic_common_type &ref, storage_type &...gpool) ENTT_NOEXCEPT + : handler{&ref}, + pools{&gpool...} {} + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using base_type = basic_common_type; + /*! @brief Random access iterator type. */ + using iterator = typename base_type::iterator; + /*! @brief Reversed iterator type. */ + using reverse_iterator = typename base_type::reverse_iterator; + /*! @brief Iterable group type. */ + using iterable = iterable_adaptor; + + /*! @brief Default constructor to use to create empty, invalid groups. */ + basic_group() ENTT_NOEXCEPT + : handler{} {} + + /** + * @brief Returns a const reference to the underlying handler. + * @return A const reference to the underlying handler. + */ + const base_type &handle() const ENTT_NOEXCEPT { + return *handler; + } + + /** + * @brief Returns the storage for a given component type. + * @tparam Comp Type of component of which to return the storage. + * @return The storage for the given component type. + */ + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get *>(pools); + } + + /** + * @brief Returns the storage for a given component type. + * @tparam Comp Index of component of which to return the storage. + * @return The storage for the given component type. + */ + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get(pools); + } + + /** + * @brief Returns the number of entities that have the given components. + * @return Number of entities that have the given components. + */ + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return *this ? handler->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 ENTT_NOEXCEPT { + return *this ? handler->capacity() : size_type{}; + } + + /*! @brief Requests the removal of unused capacity. */ + void shrink_to_fit() { + if(*this) { + handler->shrink_to_fit(); + } + } + + /** + * @brief Checks whether a group is empty. + * @return True if the group is empty, false otherwise. + */ + [[nodiscard]] bool empty() const ENTT_NOEXCEPT { + return !*this || handler->empty(); + } + + /** + * @brief Returns an iterator to the first entity of the group. + * + * The returned iterator points 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 ENTT_NOEXCEPT { + return *this ? handler->begin() : iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the group. + * + * The returned iterator points to the entity following the last entity of + * the group. Attempting to dereference the returned iterator results in + * undefined behavior. + * + * @return An iterator to the entity following the last entity of the + * group. + */ + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return *this ? handler->end() : iterator{}; + } + + /** + * @brief Returns an iterator to the first entity of the reversed group. + * + * The returned iterator points 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 ENTT_NOEXCEPT { + return *this ? handler->rbegin() : reverse_iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the reversed + * group. + * + * The returned iterator points to the entity following the last entity of + * the reversed group. Attempting to dereference the returned iterator + * results in undefined behavior. + * + * @return An iterator to the entity following the last entity of the + * reversed group. + */ + [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT { + return *this ? handler->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 ENTT_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 ENTT_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 ENTT_NOEXCEPT { + const auto it = *this ? handler->find(entt) : iterator{}; + return it != end() && *it == entt ? it : end(); + } + + /** + * @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()[pos]; + } + + /** + * @brief Checks if a group is properly initialized. + * @return True if the group is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return handler != 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 ENTT_NOEXCEPT { + return *this && handler->contains(entt); + } + + /** + * @brief Returns the components assigned to the given entity. + * + * Prefer this function instead of `registry::get` during iterations. It has + * far better performance than its counterpart. + * + * @warning + * Attempting to use an invalid component type results in a compilation + * error. Attempting to use an entity that doesn't belong to the group + * results in undefined behavior. + * + * @tparam Comp Types of components to get. + * @param entt A valid identifier. + * @return The components assigned to the entity. + */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + ENTT_ASSERT(contains(entt), "Group does not contain entity"); + + if constexpr(sizeof...(Comp) == 0) { + return std::tuple_cat(std::get *>(pools)->get_as_tuple(entt)...); + } else if constexpr(sizeof...(Comp) == 1) { + return (std::get *>(pools)->get(entt), ...); + } else { + return std::tuple_cat(std::get *>(pools)->get_as_tuple(entt)...); + } + } + + /** + * @brief Iterates entities and components 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 components. The + * _constness_ of the components 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{}, std::declval().get({})))>) { + std::apply(func, std::tuple_cat(std::make_tuple(entt), get(entt))); + } else { + std::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 components. The _constness_ of the + * components 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 ENTT_NOEXCEPT { + return handler ? iterable{extended_group_iterator{handler->begin(), pools}, extended_group_iterator{handler->end(), pools}} + : iterable{extended_group_iterator{{}, pools}, extended_group_iterator{{}, pools}}; + } + + /** + * @brief Sort a group according to the given comparison function. + * + * Sort the group so that iterating it with a couple of iterators returns + * entities and components in the expected order. See `begin` and `end` for + * more details. + * + * 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(std::tuple, std::tuple); + * bool(const Component &..., const Component &...); + * bool(const Entity, const Entity); + * @endcode + * + * Where `Component` 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 Comp Optional types of components 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...(Comp) == 0) { + static_assert(std::is_invocable_v, "Invalid comparison function"); + handler->sort(std::move(compare), std::move(algo), std::forward(args)...); + } else { + auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) { + if constexpr(sizeof...(Comp) == 1) { + return compare((std::get *>(pools)->get(lhs), ...), (std::get *>(pools)->get(rhs), ...)); + } else { + return compare(std::forward_as_tuple(std::get *>(pools)->get(lhs)...), std::forward_as_tuple(std::get *>(pools)->get(rhs)...)); + } + }; + + handler->sort(std::move(comp), std::move(algo), std::forward(args)...); + } + } + } + + /** + * @brief Sort the shared pool of entities according to the given component. + * + * Non-owning groups of the same type share with the registry a pool of + * entities with its own order that doesn't depend on the order of any pool + * of components. Users can order the underlying data structure so that it + * respects the order of the pool of the given component. + * + * @note + * The shared pool of entities and thus its order is affected by the changes + * to each and every pool that it tracks. Therefore changes to those pools + * can quickly ruin the order imposed to the pool of entities shared between + * the non-owning groups. + * + * @tparam Comp Type of component to use to impose the order. + */ + template + void sort() const { + if(*this) { + handler->respect(*std::get *>(pools)); + } + } + +private: + base_type *const handler; + const std::tuple *...> pools; +}; + +/** + * @brief Owning group. + * + * Owning groups return all entities and only the entities that have at least + * the given components. Moreover: + * + * * It's guaranteed that the entity list is tightly packed in memory for fast + * iterations. + * * It's guaranteed that the lists of owned components are tightly packed in + * memory for even faster iterations and to allow direct access. + * * They stay true to the order of the owned components and all instances have + * the same order in memory. + * + * The more types of components are owned by a group, the faster it is to + * iterate them. + * + * @b Important + * + * Iterators aren't invalidated if: + * + * * New instances of the given components are created and assigned to entities. + * * The entity currently pointed is modified (as an example, if one of the + * given components is removed from the entity to which the iterator points). + * * The entity currently pointed is destroyed. + * + * In all other cases, modifying the pools iterated by the group in any way + * invalidates all the iterators and using them results in undefined behavior. + * + * @note + * Groups share references to the underlying data structures of the registry + * that generated them. Therefore any change to the entities and to the + * components made by means of the registry are immediately reflected by all the + * groups. + * Moreover, sorting an owning group affects all the instance of the same group + * (it means that users don't have to call `sort` on each instance to sort all + * of them because they share the underlying data structure). + * + * @warning + * Lifetime of a group must not overcome that of the registry that generated it. + * In any other case, attempting to use a group results in undefined behavior. + * + * @tparam Entity A valid entity type (see entt_traits for more details). + * @tparam Owned Types of components owned by the group. + * @tparam Get Types of components observed by the group. + * @tparam Exclude Types of components used to filter the group. + */ +template +class basic_group, get_t, exclude_t> { + /*! @brief A registry is allowed to create groups. */ + friend class basic_registry; + + template + using storage_type = constness_as_t>::storage_type, Comp>; + + using basic_common_type = std::common_type_t::base_type..., typename storage_type::base_type...>; + + class extended_group_iterator final { + template + auto index_to_element(storage_type &cpool) const { + if constexpr(ignore_as_empty_v>) { + return std::make_tuple(); + } else { + return std::forward_as_tuple(cpool.rbegin()[it.index()]); + } + } + + public: + using difference_type = std::ptrdiff_t; + using value_type = decltype(std::tuple_cat(std::tuple{}, std::declval().get({}))); + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + extended_group_iterator() = default; + + template + extended_group_iterator(typename basic_common_type::iterator from, const std::tuple *..., storage_type *...> &cpools) + : it{from}, + pools{cpools} {} + + extended_group_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } + + extended_group_iterator operator++(int) ENTT_NOEXCEPT { + extended_group_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return std::tuple_cat( + std::make_tuple(*it), + index_to_element(*std::get *>(pools))..., + std::get *>(pools)->get_as_tuple(*it)...); + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + [[nodiscard]] bool operator==(const extended_group_iterator &other) const ENTT_NOEXCEPT { + return other.it == it; + } + + [[nodiscard]] bool operator!=(const extended_group_iterator &other) const ENTT_NOEXCEPT { + return !(*this == other); + } + + private: + typename basic_common_type::iterator it; + std::tuple *..., storage_type *...> pools; + }; + + basic_group(const std::size_t &extent, storage_type &...opool, storage_type &...gpool) ENTT_NOEXCEPT + : pools{&opool..., &gpool...}, + length{&extent} {} + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using base_type = basic_common_type; + /*! @brief Random access iterator type. */ + using iterator = typename base_type::iterator; + /*! @brief Reversed iterator type. */ + using reverse_iterator = typename base_type::reverse_iterator; + /*! @brief Iterable group type. */ + using iterable = iterable_adaptor; + + /*! @brief Default constructor to use to create empty, invalid groups. */ + basic_group() ENTT_NOEXCEPT + : length{} {} + + /** + * @brief Returns the storage for a given component type. + * @tparam Comp Type of component of which to return the storage. + * @return The storage for the given component type. + */ + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get *>(pools); + } + + /** + * @brief Returns the storage for a given component type. + * @tparam Comp Index of component of which to return the storage. + * @return The storage for the given component type. + */ + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get(pools); + } + + /** + * @brief Returns the number of entities that have the given components. + * @return Number of entities that have the given components. + */ + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return *this ? *length : size_type{}; + } + + /** + * @brief Checks whether a group is empty. + * @return True if the group is empty, false otherwise. + */ + [[nodiscard]] bool empty() const ENTT_NOEXCEPT { + return !*this || !*length; + } + + /** + * @brief Returns an iterator to the first entity of the group. + * + * The returned iterator points 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 ENTT_NOEXCEPT { + return *this ? (std::get<0>(pools)->base_type::end() - *length) : iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the group. + * + * The returned iterator points to the entity following the last entity of + * the group. Attempting to dereference the returned iterator results in + * undefined behavior. + * + * @return An iterator to the entity following the last entity of the + * group. + */ + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return *this ? std::get<0>(pools)->base_type::end() : iterator{}; + } + + /** + * @brief Returns an iterator to the first entity of the reversed group. + * + * The returned iterator points 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 ENTT_NOEXCEPT { + return *this ? std::get<0>(pools)->base_type::rbegin() : reverse_iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the reversed + * group. + * + * The returned iterator points to the entity following the last entity of + * the reversed group. Attempting to dereference the returned iterator + * results in undefined behavior. + * + * @return An iterator to the entity following the last entity of the + * reversed group. + */ + [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT { + return *this ? (std::get<0>(pools)->base_type::rbegin() + *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 ENTT_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 ENTT_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 ENTT_NOEXCEPT { + const auto it = *this ? std::get<0>(pools)->find(entt) : iterator{}; + return it != end() && it >= begin() && *it == entt ? it : end(); + } + + /** + * @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()[pos]; + } + + /** + * @brief Checks if a group is properly initialized. + * @return True if the group is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return length != 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 ENTT_NOEXCEPT { + return *this && std::get<0>(pools)->contains(entt) && (std::get<0>(pools)->index(entt) < (*length)); + } + + /** + * @brief Returns the components assigned to the given entity. + * + * Prefer this function instead of `registry::get` during iterations. It has + * far better performance than its counterpart. + * + * @warning + * Attempting to use an invalid component type results in a compilation + * error. Attempting to use an entity that doesn't belong to the group + * results in undefined behavior. + * + * @tparam Comp Types of components to get. + * @param entt A valid identifier. + * @return The components assigned to the entity. + */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + ENTT_ASSERT(contains(entt), "Group does not contain entity"); + + if constexpr(sizeof...(Comp) == 0) { + return std::tuple_cat(std::get *>(pools)->get_as_tuple(entt)..., std::get *>(pools)->get_as_tuple(entt)...); + } else if constexpr(sizeof...(Comp) == 1) { + return (std::get *>(pools)->get(entt), ...); + } else { + return std::tuple_cat(std::get *>(pools)->get_as_tuple(entt)...); + } + } + + /** + * @brief Iterates entities and components 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 components. The + * _constness_ of the components 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{}, std::declval().get({})))>) { + std::apply(func, args); + } else { + std::apply([&func](auto, auto &&...less) { func(std::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 components. The _constness_ of the + * components 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 ENTT_NOEXCEPT { + iterator last = length ? std::get<0>(pools)->basic_common_type::end() : iterator{}; + return {extended_group_iterator{last - *length, pools}, extended_group_iterator{last, pools}}; + } + + /** + * @brief Sort a group according to the given comparison function. + * + * Sort the group so that iterating it with a couple of iterators returns + * entities and components in the expected order. See `begin` and `end` for + * more details. + * + * 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(std::tuple, std::tuple); + * bool(const Component &, const Component &); + * bool(const Entity, const Entity); + * @endcode + * + * Where `Component` 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 Comp Optional types of components 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 { + auto *cpool = std::get<0>(pools); + + if constexpr(sizeof...(Comp) == 0) { + static_assert(std::is_invocable_v, "Invalid comparison function"); + cpool->sort_n(*length, std::move(compare), std::move(algo), std::forward(args)...); + } else { + auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) { + if constexpr(sizeof...(Comp) == 1) { + return compare((std::get *>(pools)->get(lhs), ...), (std::get *>(pools)->get(rhs), ...)); + } else { + return compare(std::forward_as_tuple(std::get *>(pools)->get(lhs)...), std::forward_as_tuple(std::get *>(pools)->get(rhs)...)); + } + }; + + cpool->sort_n(*length, std::move(comp), std::move(algo), std::forward(args)...); + } + + [this](auto *head, auto *...other) { + for(auto next = *length; next; --next) { + const auto pos = next - 1; + [[maybe_unused]] const auto entt = head->data()[pos]; + (other->swap_elements(other->data()[pos], entt), ...); + } + }(std::get *>(pools)...); + } + +private: + const std::tuple *..., storage_type *...> pools; + const size_type *const length; +}; + +} // namespace entt + +#endif + +// #include "entity/handle.hpp" +#ifndef ENTT_ENTITY_HANDLE_HPP +#define ENTT_ENTITY_HANDLE_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/type_traits.hpp" + +// #include "fwd.hpp" + +// #include "registry.hpp" +#ifndef ENTT_ENTITY_REGISTRY_HPP +#define ENTT_ENTITY_REGISTRY_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../container/dense_map.hpp" +#ifndef ENTT_CONTAINER_DENSE_MAP_HPP +#define ENTT_CONTAINER_DENSE_MAP_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "../core/compressed_pair.hpp" +#ifndef ENTT_CORE_COMPRESSED_PAIR_HPP +#define ENTT_CORE_COMPRESSED_PAIR_HPP + +#include +#include +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP + +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "fwd.hpp" +#ifndef ENTT_CORE_FWD_HPP +#define ENTT_CORE_FWD_HPP + +#include +#include +// #include "../config/config.h" + + +namespace entt { + +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<>; + +} // namespace entt + +#endif + + +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 TURN_OFF_DOXYGEN */ 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 Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; + +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; + +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; + +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; + +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::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 = std::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 Type 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 Type 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 = Type; +}; + +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; + +/** + * @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 +constexpr 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 = typename 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 = typename type_list_cat::type; + +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; + +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; +}; + +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A type list. + */ +template +using type_list_unique_t = typename type_list_unique::type; + +/** + * @brief Provides the member constant `value` 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>: std::disjunction...> {}; + +/** + * @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 = typename type_list_diff::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 value. */ + static constexpr auto value = Value; +}; + +/** + * @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 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 +constexpr 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 = typename 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 = typename value_list_cat::type; + +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::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>: std::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>: std::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 std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::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>: std::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` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; + +/*! @copydoc is_complete */ +template +struct is_complete>: std::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` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_iterator_category: std::false_type {}; + +template +struct has_iterator_category::iterator_category>>: std::true_type {}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : 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` 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 + : std::conjunction, std::negation>> {}; + +/** + * @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` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; + +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; + +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_tuple_size_value: std::false_type {}; + +template +struct has_tuple_size_value::value)>>: std::true_type {}; + +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} + +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} + +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; + } +} + +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); + } +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; + +/** + * @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 = std::remove_const_t; +}; + +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; + +/** + * @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 = typename constness_as::type; + +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); + + 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 = std::remove_pointer_t()))>; +}; + +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct compressed_pair_element { + using reference = Type &; + using const_reference = const Type &; + + template>> + compressed_pair_element() + : value{} {} + + template>, compressed_pair_element>>> + compressed_pair_element(Args &&args) + : value{std::forward(args)} {} + + template + compressed_pair_element(std::tuple args, std::index_sequence) + : value{std::forward(std::get(args))...} {} + + [[nodiscard]] reference get() ENTT_NOEXCEPT { + return value; + } + + [[nodiscard]] const_reference get() const ENTT_NOEXCEPT { + return value; + } + +private: + Type value; +}; + +template +struct compressed_pair_element>>: Type { + using reference = Type &; + using const_reference = const Type &; + using base_type = Type; + + template>> + compressed_pair_element() + : base_type{} {} + + template>, compressed_pair_element>>> + compressed_pair_element(Args &&args) + : base_type{std::forward(args)} {} + + template + compressed_pair_element(std::tuple args, std::index_sequence) + : base_type{std::forward(std::get(args))...} {} + + [[nodiscard]] reference get() ENTT_NOEXCEPT { + return *this; + } + + [[nodiscard]] const_reference get() const ENTT_NOEXCEPT { + return *this; + } +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @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. + * + * @tparam Dummy Dummy template parameter used for internal purposes. + */ + template && std::is_default_constructible_v>> + constexpr compressed_pair() + : 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) = 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) + : first_base{std::forward(arg)}, + second_base{std::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(std::piecewise_construct_t, std::tuple args, std::tuple other) + : first_base{std::move(args), std::index_sequence_for{}}, + second_base{std::move(other), std::index_sequence_for{}} {} + + /** + * @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) = default; + + /** + * @brief Returns the first element that a pair stores. + * @return The first element that a pair stores. + */ + [[nodiscard]] first_type &first() ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /*! @copydoc first */ + [[nodiscard]] const first_type &first() const ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /** + * @brief Returns the second element that a pair stores. + * @return The second element that a pair stores. + */ + [[nodiscard]] second_type &second() ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /*! @copydoc second */ + [[nodiscard]] const second_type &second() const ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /** + * @brief Swaps two compressed pair objects. + * @param other The compressed pair to swap with. + */ + void swap(compressed_pair &other) { + using std::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 + decltype(auto) get() ENTT_NOEXCEPT { + if constexpr(Index == 0u) { + return first(); + } else { + static_assert(Index == 1u, "Index out of bounds"); + return second(); + } + } + + /*! @copydoc get */ + template + decltype(auto) get() const ENTT_NOEXCEPT { + if constexpr(Index == 0u) { + return first(); + } else { + static_assert(Index == 1u, "Index out of bounds"); + 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, std::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 +inline void swap(compressed_pair &lhs, compressed_pair &rhs) { + lhs.swap(rhs); +} + +} // namespace entt + +// disable structured binding support for clang 6, it messes when specializing tuple_size +#if !defined __clang_major__ || __clang_major__ > 6 +namespace std { + +/** + * @brief `std::tuple_size` specialization for `compressed_pair`s. + * @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 +struct tuple_size>: integral_constant {}; + +/** + * @brief `std::tuple_element` specialization for `compressed_pair`s. + * @tparam Index The index of the type to return. + * @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 +struct tuple_element>: conditional { + static_assert(Index < 2u, "Index out of bounds"); +}; + +} // namespace std +#endif + +#endif + +// #include "../core/iterator.hpp" +#ifndef ENTT_CORE_ITERATOR_HPP +#define ENTT_CORE_ITERATOR_HPP + +#include +#include +#include +// #include "../config/config.h" + + +namespace entt { + +/** + * @brief Helper type to use as pointer with input iterators. + * @tparam Type of wrapped value. + */ +template +struct input_iterator_pointer final { + /*! @brief Pointer type. */ + using pointer = Type *; + + /*! @brief Default copy constructor, deleted on purpose. */ + input_iterator_pointer(const input_iterator_pointer &) = delete; + + /*! @brief Default move constructor. */ + input_iterator_pointer(input_iterator_pointer &&) = default; + + /** + * @brief Constructs a proxy object by move. + * @param val Value to use to initialize the proxy object. + */ + input_iterator_pointer(Type &&val) + : value{std::move(val)} {} + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This proxy object. + */ + input_iterator_pointer &operator=(const input_iterator_pointer &) = delete; + + /** + * @brief Default move assignment operator. + * @return This proxy object. + */ + input_iterator_pointer &operator=(input_iterator_pointer &&) = default; + + /** + * @brief Access operator for accessing wrapped values. + * @return A pointer to the wrapped value. + */ + [[nodiscard]] pointer operator->() ENTT_NOEXCEPT { + return std::addressof(value); + } + +private: + Type value; +}; + +/** + * @brief Utility class to create an iterable object from a pair of iterators. + * @tparam It Type of iterator. + * @tparam Sentinel Type of sentinel. + */ +template +struct iterable_adaptor final { + /*! @brief Value type. */ + using value_type = typename std::iterator_traits::value_type; + /*! @brief Iterator type. */ + using iterator = It; + /*! @brief Sentinel type. */ + using sentinel = Sentinel; + + /*! @brief Default constructor. */ + iterable_adaptor() = default; + + /** + * @brief Creates an iterable object from a pair of iterators. + * @param from Begin iterator. + * @param to End iterator. + */ + iterable_adaptor(iterator from, sentinel to) + : first{from}, + last{to} {} + + /** + * @brief Returns an iterator to the beginning. + * @return An iterator to the first element of the range. + */ + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return first; + } + + /** + * @brief Returns an iterator to the end. + * @return An iterator to the element following the last element of the + * range. + */ + [[nodiscard]] sentinel end() const ENTT_NOEXCEPT { + return last; + } + + /*! @copydoc begin */ + [[nodiscard]] iterator cbegin() const ENTT_NOEXCEPT { + return begin(); + } + + /*! @copydoc end */ + [[nodiscard]] sentinel cend() const ENTT_NOEXCEPT { + return end(); + } + +private: + It first; + Sentinel last; +}; + +} // namespace entt + +#endif + +// #include "../core/memory.hpp" +#ifndef ENTT_CORE_MEMORY_HPP +#define ENTT_CORE_MEMORY_HPP + +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" + + +namespace entt { + +/** + * @brief Unwraps fancy pointers, does nothing otherwise (waiting for C++20). + * @tparam Type Pointer type. + * @param ptr Fancy or raw pointer. + * @return A raw pointer that represents the address of the original pointer. + */ +template +[[nodiscard]] constexpr auto to_address(Type &&ptr) ENTT_NOEXCEPT { + if constexpr(std::is_pointer_v>>) { + return ptr; + } else { + return to_address(std::forward(ptr).operator->()); + } +} + +/** + * @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) ENTT_NOEXCEPT { + if constexpr(std::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) ENTT_NOEXCEPT { + if constexpr(std::allocator_traits::propagate_on_container_move_assignment::value) { + lhs = std::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) ENTT_NOEXCEPT { + ENTT_ASSERT(std::allocator_traits::propagate_on_container_swap::value || lhs == rhs, "Cannot swap the containers"); + + if constexpr(std::allocator_traits::propagate_on_container_swap::value) { + using std::swap; + swap(lhs, rhs); + } +} + +/** + * @brief Checks whether a value is a power of two or not. + * @param value A value that may or may not be a power of two. + * @return True if the value is a power of two, false otherwise. + */ +[[nodiscard]] inline constexpr bool is_power_of_two(const std::size_t value) ENTT_NOEXCEPT { + return value && ((value & (value - 1)) == 0); +} + +/** + * @brief Computes the smallest power of two greater than or equal to a value. + * @param value The value to use. + * @return The smallest power of two greater than or equal to the given value. + */ +[[nodiscard]] inline constexpr std::size_t next_power_of_two(const std::size_t value) ENTT_NOEXCEPT { + ENTT_ASSERT(value < (std::size_t{1u} << (std::numeric_limits::digits - 1)), "Numeric limits exceeded"); + std::size_t curr = value - (value != 0u); + + for(int next = 1; next < std::numeric_limits::digits; next = next * 2) { + curr |= curr >> next; + } + + return ++curr; +} + +/** + * @brief Fast module utility function (powers of two only). + * @param value A value for which to calculate the modulus. + * @param mod _Modulus_, it must be a power of two. + * @return The common remainder. + */ +[[nodiscard]] inline constexpr std::size_t fast_mod(const std::size_t value, const std::size_t mod) ENTT_NOEXCEPT { + ENTT_ASSERT(is_power_of_two(mod), "Value must be a power of two"); + return value & (mod - 1u); +} + +/** + * @brief Deleter for allocator-aware unique pointers (waiting for C++20). + * @tparam Args Types of arguments to use to construct the object. + */ +template +struct allocation_deleter: private Allocator { + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Pointer type. */ + using pointer = typename std::allocator_traits::pointer; + + /** + * @brief Inherited constructors. + * @param alloc The allocator to use. + */ + allocation_deleter(const allocator_type &alloc) + : Allocator{alloc} {} + + /** + * @brief Destroys the pointed object and deallocates its memory. + * @param ptr A valid pointer to an object of the given type. + */ + void operator()(pointer ptr) { + using alloc_traits = typename std::allocator_traits; + alloc_traits::destroy(*this, to_address(ptr)); + alloc_traits::deallocate(*this, ptr, 1u); + } +}; + +/** + * @brief Allows `std::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 +auto allocate_unique(Allocator &allocator, Args &&...args) { + static_assert(!std::is_array_v, "Array types are not supported"); + + using alloc_traits = typename std::allocator_traits::template rebind_traits; + using allocator_type = typename alloc_traits::allocator_type; + + allocator_type alloc{allocator}; + auto ptr = alloc_traits::allocate(alloc, 1u); + + ENTT_TRY { + alloc_traits::construct(alloc, to_address(ptr), std::forward(args)...); + } + ENTT_CATCH { + alloc_traits::deallocate(alloc, ptr, 1u); + ENTT_THROW; + } + + return std::unique_ptr>{ptr, alloc}; +} + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct uses_allocator_construction { + template + static constexpr auto args([[maybe_unused]] const Allocator &allocator, Params &&...params) ENTT_NOEXCEPT { + if constexpr(!std::uses_allocator_v && std::is_constructible_v) { + return std::forward_as_tuple(std::forward(params)...); + } else { + static_assert(std::uses_allocator_v, "Ill-formed request"); + + if constexpr(std::is_constructible_v) { + return std::tuple(std::allocator_arg, allocator, std::forward(params)...); + } else { + static_assert(std::is_constructible_v, "Ill-formed request"); + return std::forward_as_tuple(std::forward(params)..., allocator); + } + } + } +}; + +template +struct uses_allocator_construction> { + using type = std::pair; + + template + static constexpr auto args(const Allocator &allocator, std::piecewise_construct_t, First &&first, Second &&second) ENTT_NOEXCEPT { + return std::make_tuple( + std::piecewise_construct, + std::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, std::forward(curr)...); }, std::forward(first)), + std::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, std::forward(curr)...); }, std::forward(second))); + } + + template + static constexpr auto args(const Allocator &allocator) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::tuple<>{}, std::tuple<>{}); + } + + template + static constexpr auto args(const Allocator &allocator, First &&first, Second &&second) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::forward(first)), std::forward_as_tuple(std::forward(second))); + } + + template + static constexpr auto args(const Allocator &allocator, const std::pair &value) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(value.first), std::forward_as_tuple(value.second)); + } + + template + static constexpr auto args(const Allocator &allocator, std::pair &&value) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::move(value.first)), std::forward_as_tuple(std::move(value.second))); + } +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @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 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 The arguments needed to create an object of the given type. + */ +template +constexpr auto uses_allocator_construction_args(const Allocator &allocator, Args &&...args) ENTT_NOEXCEPT { + return internal::uses_allocator_construction::args(allocator, std::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 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 newly created object of the given type. + */ +template +constexpr Type make_obj_using_allocator(const Allocator &allocator, Args &&...args) { + return std::make_from_tuple(internal::uses_allocator_construction::args(allocator, std::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 Allocator Type of allocator used to manage memory and elements. + * @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 Allocator &allocator, Args &&...args) { + return std::apply([&](auto &&...curr) { return new(value) Type(std::forward(curr)...); }, internal::uses_allocator_construction::args(allocator, std::forward(args)...)); +} + +} // namespace entt + +#endif + +// #include "../core/type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP + +#include +#include +#include +#include +// #include "../config/config.h" + +// #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 TURN_OFF_DOXYGEN */ 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 Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; + +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; + +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; + +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; + +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::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 = std::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 Type 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 Type 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 = Type; +}; + +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; + +/** + * @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 +constexpr 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 = typename 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 = typename type_list_cat::type; + +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; + +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; +}; + +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A type list. + */ +template +using type_list_unique_t = typename type_list_unique::type; + +/** + * @brief Provides the member constant `value` 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>: std::disjunction...> {}; + +/** + * @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 = typename type_list_diff::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 value. */ + static constexpr auto value = Value; +}; + +/** + * @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 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 +constexpr 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 = typename 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 = typename value_list_cat::type; + +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::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>: std::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>: std::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 std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::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>: std::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` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; + +/*! @copydoc is_complete */ +template +struct is_complete>: std::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` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_iterator_category: std::false_type {}; + +template +struct has_iterator_category::iterator_category>>: std::true_type {}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : 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` 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 + : std::conjunction, std::negation>> {}; + +/** + * @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` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; + +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; + +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_tuple_size_value: std::false_type {}; + +template +struct has_tuple_size_value::value)>>: std::true_type {}; + +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} + +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} + +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; + } +} + +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); + } +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; + +/** + * @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 = std::remove_const_t; +}; + +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; + +/** + * @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 = typename constness_as::type; + +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); + + 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 = std::remove_pointer_t()))>; +}; + +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; + +} // namespace entt + +#endif + +// #include "fwd.hpp" +#ifndef ENTT_CONTAINER_FWD_HPP +#define ENTT_CONTAINER_FWD_HPP + +#include +#include + +namespace entt { + +template< + typename Key, + typename Type, + typename = std::hash, + typename = std::equal_to, + typename = std::allocator>> +class dense_map; + +template< + typename Type, + typename = std::hash, + typename = std::equal_to, + typename = std::allocator> +class dense_set; + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct dense_map_node final { + using value_type = std::pair; + + template + dense_map_node(const std::size_t pos, Args &&...args) + : next{pos}, + element{std::forward(args)...} {} + + template + dense_map_node(std::allocator_arg_t, const Allocator &allocator, const std::size_t pos, Args &&...args) + : next{pos}, + element{entt::make_obj_using_allocator(allocator, std::forward(args)...)} {} + + template + dense_map_node(std::allocator_arg_t, const Allocator &allocator, const dense_map_node &other) + : next{other.next}, + element{entt::make_obj_using_allocator(allocator, other.element)} {} + + template + dense_map_node(std::allocator_arg_t, const Allocator &allocator, dense_map_node &&other) + : next{other.next}, + element{entt::make_obj_using_allocator(allocator, std::move(other.element))} {} + + std::size_t next; + value_type element; +}; + +template +class dense_map_iterator final { + template + friend class dense_map_iterator; + + using first_type = decltype(std::as_const(std::declval()->element.first)); + using second_type = decltype((std::declval()->element.second)); + +public: + using value_type = std::pair; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + dense_map_iterator() ENTT_NOEXCEPT + : it{} {} + + dense_map_iterator(const It iter) ENTT_NOEXCEPT + : it{iter} {} + + template && std::is_constructible_v>> + dense_map_iterator(const dense_map_iterator &other) ENTT_NOEXCEPT + : it{other.it} {} + + dense_map_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } + + dense_map_iterator operator++(int) ENTT_NOEXCEPT { + dense_map_iterator orig = *this; + return ++(*this), orig; + } + + dense_map_iterator &operator--() ENTT_NOEXCEPT { + return --it, *this; + } + + dense_map_iterator operator--(int) ENTT_NOEXCEPT { + dense_map_iterator orig = *this; + return operator--(), orig; + } + + dense_map_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + it += value; + return *this; + } + + dense_map_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + dense_map_iterator copy = *this; + return (copy += value); + } + + dense_map_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); + } + + dense_map_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); + } + + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + return {it[value].element.first, it[value].element.second}; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return {it->element.first, it->element.second}; + } + + template + friend std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator==(const dense_map_iterator &, const dense_map_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator<(const dense_map_iterator &, const dense_map_iterator &) ENTT_NOEXCEPT; + +private: + It it; +}; + +template +[[nodiscard]] std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it - rhs.it; +} + +template +[[nodiscard]] bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +[[nodiscard]] bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it < rhs.it; +} + +template +[[nodiscard]] bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +template +[[nodiscard]] bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +template +[[nodiscard]] bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +template +class dense_map_local_iterator final { + template + friend class dense_map_local_iterator; + + using first_type = decltype(std::as_const(std::declval()->element.first)); + using second_type = decltype((std::declval()->element.second)); + +public: + using value_type = std::pair; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + dense_map_local_iterator() ENTT_NOEXCEPT + : it{}, + offset{} {} + + dense_map_local_iterator(It iter, const std::size_t pos) ENTT_NOEXCEPT + : it{iter}, + offset{pos} {} + + template && std::is_constructible_v>> + dense_map_local_iterator(const dense_map_local_iterator &other) ENTT_NOEXCEPT + : it{other.it}, + offset{other.offset} {} + + dense_map_local_iterator &operator++() ENTT_NOEXCEPT { + return offset = it[offset].next, *this; + } + + dense_map_local_iterator operator++(int) ENTT_NOEXCEPT { + dense_map_local_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return {it[offset].element.first, it[offset].element.second}; + } + + [[nodiscard]] std::size_t index() const ENTT_NOEXCEPT { + return offset; + } + +private: + It it; + std::size_t offset; +}; + +template +[[nodiscard]] bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() == rhs.index(); +} + +template +[[nodiscard]] bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @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 std::size_t minimum_capacity = 8u; + + using node_type = internal::dense_map_node; + using alloc_traits = typename std::allocator_traits; + static_assert(std::is_same_v>, "Invalid value type"); + using sparse_container_type = std::vector>; + using packed_container_type = std::vector>; + + template + [[nodiscard]] std::size_t key_to_bucket(const Other &key) const ENTT_NOEXCEPT { + return fast_mod(sparse.second()(key), bucket_count()); + } + + template + [[nodiscard]] auto constrained_find(const Other &key, std::size_t bucket) { + for(auto it = begin(bucket), last = end(bucket); it != last; ++it) { + if(packed.second()(it->first, key)) { + return begin() + static_cast(it.index()); + } + } + + return end(); + } + + template + [[nodiscard]] auto constrained_find(const Other &key, std::size_t bucket) const { + for(auto it = cbegin(bucket), last = cend(bucket); it != last; ++it) { + if(packed.second()(it->first, key)) { + return cbegin() + static_cast(it.index()); + } + } + + 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 std::make_pair(it, false); + } + + packed.first().emplace_back(sparse.first()[index], std::piecewise_construct, std::forward_as_tuple(std::forward(key)), std::forward_as_tuple(std::forward(args)...)); + sparse.first()[index] = packed.first().size() - 1u; + rehash_if_required(); + + return std::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 = std::forward(value); + return std::make_pair(it, false); + } + + packed.first().emplace_back(sparse.first()[index], std::forward(key), std::forward(value)); + sparse.first()[index] = packed.first().size() - 1u; + rehash_if_required(); + + return std::make_pair(--end(), true); + } + + void move_and_pop(const std::size_t pos) { + if(const auto last = size() - 1u; pos != last) { + packed.first()[pos] = std::move(packed.first().back()); + size_type *curr = sparse.first().data() + key_to_bucket(packed.first().back().element.first); + for(; *curr != last; curr = &packed.first()[*curr].next) {} + *curr = pos; + } + + packed.first().pop_back(); + } + + void rehash_if_required() { + if(size() > (bucket_count() * max_load_factor())) { + rehash(bucket_count() * 2u); + } + } + +public: + /*! @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 = std::pair; + /*! @brief Unsigned integer type. */ + using size_type = std::size_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 Allocator type. */ + using allocator_type = Allocator; + /*! @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 bucket_count Minimal number of buckets. + * @param allocator The allocator to use. + */ + dense_map(const size_type bucket_count, const allocator_type &allocator) + : dense_map{bucket_count, hasher{}, key_equal{}, allocator} {} + + /** + * @brief Constructs an empty container with a given allocator, hash + * function and user supplied minimal number of buckets. + * @param bucket_count Minimal number of buckets. + * @param hash Hash function to use. + * @param allocator The allocator to use. + */ + dense_map(const size_type bucket_count, const hasher &hash, const allocator_type &allocator) + : dense_map{bucket_count, 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 bucket_count 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 bucket_count, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{}) + : sparse{allocator, hash}, + packed{allocator, equal}, + threshold{default_threshold} { + rehash(bucket_count); + } + + /*! @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{std::piecewise_construct, std::forward_as_tuple(other.sparse.first(), allocator), std::forward_as_tuple(other.sparse.second())}, + packed{std::piecewise_construct, std::forward_as_tuple(other.packed.first(), allocator), std::forward_as_tuple(other.packed.second())}, + threshold{other.threshold} {} + + /*! @brief Default move constructor. */ + dense_map(dense_map &&) = 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{std::piecewise_construct, std::forward_as_tuple(std::move(other.sparse.first()), allocator), std::forward_as_tuple(std::move(other.sparse.second()))}, + packed{std::piecewise_construct, std::forward_as_tuple(std::move(other.packed.first()), allocator), std::forward_as_tuple(std::move(other.packed.second()))}, + threshold{other.threshold} {} + + /** + * @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 &&) = default; + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return sparse.first().get_allocator(); + } + + /** + * @brief Returns an iterator to the beginning. + * + * The returned iterator points to the first instance of the internal array. + * 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 ENTT_NOEXCEPT { + return packed.first().begin(); + } + + /*! @copydoc cbegin */ + [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT { + return cbegin(); + } + + /*! @copydoc begin */ + [[nodiscard]] iterator begin() ENTT_NOEXCEPT { + return packed.first().begin(); + } + + /** + * @brief Returns an iterator to the end. + * + * The returned iterator points to the element following the last instance + * of the internal array. Attempting to dereference the returned iterator + * results in undefined behavior. + * + * @return An iterator to the element following the last instance of the + * internal array. + */ + [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { + return packed.first().end(); + } + + /*! @copydoc cend */ + [[nodiscard]] const_iterator end() const ENTT_NOEXCEPT { + return cend(); + } + + /*! @copydoc end */ + [[nodiscard]] iterator end() ENTT_NOEXCEPT { + return packed.first().end(); + } + + /** + * @brief Checks whether a container is empty. + * @return True if the container is empty, false otherwise. + */ + [[nodiscard]] bool empty() const ENTT_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 ENTT_NOEXCEPT { + return packed.first().size(); + } + + /*! @brief Clears the container. */ + void clear() ENTT_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. + */ + std::pair insert(const value_type &value) { + return insert_or_do_nothing(value.first, value.second); + } + + /*! @copydoc insert */ + std::pair insert(value_type &&value) { + return insert_or_do_nothing(std::move(value.first), std::move(value.second)); + } + + /** + * @copydoc insert + * @tparam Arg Type of the key-value pair to insert into the container. + */ + template + std::enable_if_t, std::pair> + insert(Arg &&value) { + return insert_or_do_nothing(std::forward(value).first, std::forward(value).second); + } + + /** + * @brief Inserts elements into the container, if their keys do not exist. + * @tparam It Type of input iterator. + * @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. + */ + template + void insert(It first, It 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 + std::pair insert_or_assign(const key_type &key, Arg &&value) { + return insert_or_overwrite(key, std::forward(value)); + } + + /*! @copydoc insert_or_assign */ + template + std::pair insert_or_assign(key_type &&key, Arg &&value) { + return insert_or_overwrite(std::move(key), std::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 + std::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(std::forward(args).first..., std::forward(args).second...); + } else if constexpr(sizeof...(Args) == 2u) { + return insert_or_do_nothing(std::forward(args)...); + } else { + auto &node = packed.first().emplace_back(packed.first().size(), std::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 std::make_pair(it, false); + } + + std::swap(node.next, sparse.first()[index]); + rehash_if_required(); + + return std::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 + std::pair try_emplace(const key_type &key, Args &&...args) { + return insert_or_do_nothing(key, std::forward(args)...); + } + + /*! @copydoc try_emplace */ + template + std::pair try_emplace(key_type &&key, Args &&...args) { + return insert_or_do_nothing(std::move(key), std::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()[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().data() + key_to_bucket(key); *curr != (std::numeric_limits::max)(); 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 Exchanges the contents with those of a given container. + * @param other Container to exchange the content with. + */ + void swap(dense_map &other) { + using std::swap; + swap(sparse, other.sparse); + swap(packed, other.packed); + swap(threshold, other.threshold); + } + + /** + * @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 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(std::move(key)).first->second; + } + + /** + * @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 + * value. + * @tparam Other Type of the key value of an element to search for. + * @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. + */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + find(const Other &key) { + return constrained_find(key, key_to_bucket(key)); + } + + /*! @copydoc find */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + find(const Other &key) const { + return constrained_find(key, key_to_bucket(key)); + } + + /** + * @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. + * @tparam Other Type of the key value of an element to search for. + * @param key Key value of an element to search for. + * @return True if there is such an element, false otherwise. + */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + contains(const Other &key) const { + 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().begin(), 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().begin(), 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 {packed.first().begin(), (std::numeric_limits::max)()}; + } + + /** + * @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([[maybe_unused]] 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 {packed.first().begin(), (std::numeric_limits::max)()}; + } + + /** + * @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(std::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 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 count New number of buckets. + */ + void rehash(const size_type count) { + auto value = (std::max)(count, minimum_capacity); + value = (std::max)(value, static_cast(size() / max_load_factor())); + + if(const auto sz = next_power_of_two(value); sz != bucket_count()) { + sparse.first().resize(sz); + std::fill(sparse.first().begin(), sparse.first().end(), (std::numeric_limits::max)()); + + for(size_type pos{}, last = size(); pos < last; ++pos) { + const auto index = key_to_bucket(packed.first()[pos].element.first); + packed.first()[pos].next = std::exchange(sparse.first()[index], pos); + } + } + } + + /** + * @brief Reserves space for at least the specified number of elements and + * regenerates the hash table. + * @param count New number of elements. + */ + void reserve(const size_type count) { + packed.first().reserve(count); + rehash(static_cast(std::ceil(count / 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; +}; + +} // namespace entt + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace std { + +template +struct uses_allocator, Allocator> + : std::true_type {}; + +} // namespace std + +/** + * Internal details not to be documented. + * @endcond + */ + +#endif + +// #include "../core/algorithm.hpp" + +// #include "../core/any.hpp" + +// #include "../core/fwd.hpp" + +// #include "../core/iterator.hpp" + +// #include "../core/type_info.hpp" + +// #include "../core/type_traits.hpp" + +// #include "../core/utility.hpp" +#ifndef ENTT_CORE_UTILITY_HPP +#define ENTT_CORE_UTILITY_HPP + +#include +// #include "../config/config.h" + + +namespace entt { + +/*! @brief Identity function object (waiting for C++20). */ +struct identity { + /*! @brief Indicates that this is a transparent function object. */ + using is_transparent = void; + + /** + * @brief Returns its argument unchanged. + * @tparam Type Type of the argument. + * @param value The actual argument. + * @return The submitted value as-is. + */ + template + [[nodiscard]] constexpr Type &&operator()(Type &&value) const ENTT_NOEXCEPT { + return std::forward(value); + } +}; + +/** + * @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) ENTT_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) ENTT_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. + */ + y_combinator(Func recursive) + : func{std::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 + decltype(auto) operator()(Args &&...args) const { + return func(*this, std::forward(args)...); + } + + /*! @copydoc operator()() */ + template + decltype(auto) operator()(Args &&...args) { + return func(*this, std::forward(args)...); + } + +private: + Func func; +}; + +} // namespace entt + +#endif + +// #include "component.hpp" + +// #include "entity.hpp" + +// #include "fwd.hpp" + +// #include "group.hpp" +#ifndef ENTT_ENTITY_GROUP_HPP +#define ENTT_ENTITY_GROUP_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/iterator.hpp" + +// #include "../core/type_traits.hpp" + +// #include "component.hpp" + +// #include "entity.hpp" + +// #include "fwd.hpp" + +// #include "sparse_set.hpp" + +// #include "storage.hpp" + +// #include "utility.hpp" + + +namespace entt { + +/** + * @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 have at + * least the given components. Moreover, it's guaranteed that the entity list + * is tightly packed in memory for fast iterations. + * + * @b Important + * + * Iterators aren't invalidated if: + * + * * New instances of the given components are created and assigned to entities. + * * The entity currently pointed is modified (as an example, if one of the + * given components is removed from the entity to which the iterator points). + * * The entity currently pointed is destroyed. + * + * In all other cases, modifying the pools iterated by the group in any way + * invalidates all the iterators and using them results in undefined behavior. + * + * @note + * Groups share references to the underlying data structures of the registry + * that generated them. Therefore any change to the entities and to the + * components made by means of the registry are immediately reflected by all the + * groups.
+ * Moreover, sorting a non-owning group affects all the instances of the same + * group (it means that users don't have to call `sort` on each instance to sort + * all of them because they _share_ entities and components). + * + * @warning + * Lifetime of a group must not overcome that of the registry that generated it. + * In any other case, attempting to use a group results in undefined behavior. + * + * @tparam Entity A valid entity type (see entt_traits for more details). + * @tparam Get Type of components observed by the group. + * @tparam Exclude Types of components used to filter the group. + */ +template +class basic_group, get_t, exclude_t> { + /*! @brief A registry is allowed to create groups. */ + friend class basic_registry; + + template + using storage_type = constness_as_t>::storage_type, Comp>; + + using basic_common_type = std::common_type_t::base_type...>; + + struct extended_group_iterator final { + using difference_type = std::ptrdiff_t; + using value_type = decltype(std::tuple_cat(std::tuple{}, std::declval().get({}))); + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + extended_group_iterator() = default; + + extended_group_iterator(typename basic_common_type::iterator from, const std::tuple *...> &args) + : it{from}, + pools{args} {} + + extended_group_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } + + extended_group_iterator operator++(int) ENTT_NOEXCEPT { + extended_group_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + const auto entt = *it; + return std::tuple_cat(std::make_tuple(entt), std::get *>(pools)->get_as_tuple(entt)...); + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + [[nodiscard]] bool operator==(const extended_group_iterator &other) const ENTT_NOEXCEPT { + return other.it == it; + } + + [[nodiscard]] bool operator!=(const extended_group_iterator &other) const ENTT_NOEXCEPT { + return !(*this == other); + } + + private: + typename basic_common_type::iterator it; + std::tuple *...> pools; + }; + + basic_group(basic_common_type &ref, storage_type &...gpool) ENTT_NOEXCEPT + : handler{&ref}, + pools{&gpool...} {} + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using base_type = basic_common_type; + /*! @brief Random access iterator type. */ + using iterator = typename base_type::iterator; + /*! @brief Reversed iterator type. */ + using reverse_iterator = typename base_type::reverse_iterator; + /*! @brief Iterable group type. */ + using iterable = iterable_adaptor; + + /*! @brief Default constructor to use to create empty, invalid groups. */ + basic_group() ENTT_NOEXCEPT + : handler{} {} + + /** + * @brief Returns a const reference to the underlying handler. + * @return A const reference to the underlying handler. + */ + const base_type &handle() const ENTT_NOEXCEPT { + return *handler; + } + + /** + * @brief Returns the storage for a given component type. + * @tparam Comp Type of component of which to return the storage. + * @return The storage for the given component type. + */ + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get *>(pools); + } + + /** + * @brief Returns the storage for a given component type. + * @tparam Comp Index of component of which to return the storage. + * @return The storage for the given component type. + */ + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get(pools); + } + + /** + * @brief Returns the number of entities that have the given components. + * @return Number of entities that have the given components. + */ + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return *this ? handler->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 ENTT_NOEXCEPT { + return *this ? handler->capacity() : size_type{}; + } + + /*! @brief Requests the removal of unused capacity. */ + void shrink_to_fit() { + if(*this) { + handler->shrink_to_fit(); + } + } + + /** + * @brief Checks whether a group is empty. + * @return True if the group is empty, false otherwise. + */ + [[nodiscard]] bool empty() const ENTT_NOEXCEPT { + return !*this || handler->empty(); + } + + /** + * @brief Returns an iterator to the first entity of the group. + * + * The returned iterator points 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 ENTT_NOEXCEPT { + return *this ? handler->begin() : iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the group. + * + * The returned iterator points to the entity following the last entity of + * the group. Attempting to dereference the returned iterator results in + * undefined behavior. + * + * @return An iterator to the entity following the last entity of the + * group. + */ + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return *this ? handler->end() : iterator{}; + } + + /** + * @brief Returns an iterator to the first entity of the reversed group. + * + * The returned iterator points 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 ENTT_NOEXCEPT { + return *this ? handler->rbegin() : reverse_iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the reversed + * group. + * + * The returned iterator points to the entity following the last entity of + * the reversed group. Attempting to dereference the returned iterator + * results in undefined behavior. + * + * @return An iterator to the entity following the last entity of the + * reversed group. + */ + [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT { + return *this ? handler->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 ENTT_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 ENTT_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 ENTT_NOEXCEPT { + const auto it = *this ? handler->find(entt) : iterator{}; + return it != end() && *it == entt ? it : end(); + } + + /** + * @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()[pos]; + } + + /** + * @brief Checks if a group is properly initialized. + * @return True if the group is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return handler != 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 ENTT_NOEXCEPT { + return *this && handler->contains(entt); + } + + /** + * @brief Returns the components assigned to the given entity. + * + * Prefer this function instead of `registry::get` during iterations. It has + * far better performance than its counterpart. + * + * @warning + * Attempting to use an invalid component type results in a compilation + * error. Attempting to use an entity that doesn't belong to the group + * results in undefined behavior. + * + * @tparam Comp Types of components to get. + * @param entt A valid identifier. + * @return The components assigned to the entity. + */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + ENTT_ASSERT(contains(entt), "Group does not contain entity"); + + if constexpr(sizeof...(Comp) == 0) { + return std::tuple_cat(std::get *>(pools)->get_as_tuple(entt)...); + } else if constexpr(sizeof...(Comp) == 1) { + return (std::get *>(pools)->get(entt), ...); + } else { + return std::tuple_cat(std::get *>(pools)->get_as_tuple(entt)...); + } + } + + /** + * @brief Iterates entities and components 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 components. The + * _constness_ of the components 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{}, std::declval().get({})))>) { + std::apply(func, std::tuple_cat(std::make_tuple(entt), get(entt))); + } else { + std::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 components. The _constness_ of the + * components 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 ENTT_NOEXCEPT { + return handler ? iterable{extended_group_iterator{handler->begin(), pools}, extended_group_iterator{handler->end(), pools}} + : iterable{extended_group_iterator{{}, pools}, extended_group_iterator{{}, pools}}; + } + + /** + * @brief Sort a group according to the given comparison function. + * + * Sort the group so that iterating it with a couple of iterators returns + * entities and components in the expected order. See `begin` and `end` for + * more details. + * + * 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(std::tuple, std::tuple); + * bool(const Component &..., const Component &...); + * bool(const Entity, const Entity); + * @endcode + * + * Where `Component` 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 Comp Optional types of components 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...(Comp) == 0) { + static_assert(std::is_invocable_v, "Invalid comparison function"); + handler->sort(std::move(compare), std::move(algo), std::forward(args)...); + } else { + auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) { + if constexpr(sizeof...(Comp) == 1) { + return compare((std::get *>(pools)->get(lhs), ...), (std::get *>(pools)->get(rhs), ...)); + } else { + return compare(std::forward_as_tuple(std::get *>(pools)->get(lhs)...), std::forward_as_tuple(std::get *>(pools)->get(rhs)...)); + } + }; + + handler->sort(std::move(comp), std::move(algo), std::forward(args)...); + } + } + } + + /** + * @brief Sort the shared pool of entities according to the given component. + * + * Non-owning groups of the same type share with the registry a pool of + * entities with its own order that doesn't depend on the order of any pool + * of components. Users can order the underlying data structure so that it + * respects the order of the pool of the given component. + * + * @note + * The shared pool of entities and thus its order is affected by the changes + * to each and every pool that it tracks. Therefore changes to those pools + * can quickly ruin the order imposed to the pool of entities shared between + * the non-owning groups. + * + * @tparam Comp Type of component to use to impose the order. + */ + template + void sort() const { + if(*this) { + handler->respect(*std::get *>(pools)); + } + } + +private: + base_type *const handler; + const std::tuple *...> pools; +}; + +/** + * @brief Owning group. + * + * Owning groups return all entities and only the entities that have at least + * the given components. Moreover: + * + * * It's guaranteed that the entity list is tightly packed in memory for fast + * iterations. + * * It's guaranteed that the lists of owned components are tightly packed in + * memory for even faster iterations and to allow direct access. + * * They stay true to the order of the owned components and all instances have + * the same order in memory. + * + * The more types of components are owned by a group, the faster it is to + * iterate them. + * + * @b Important + * + * Iterators aren't invalidated if: + * + * * New instances of the given components are created and assigned to entities. + * * The entity currently pointed is modified (as an example, if one of the + * given components is removed from the entity to which the iterator points). + * * The entity currently pointed is destroyed. + * + * In all other cases, modifying the pools iterated by the group in any way + * invalidates all the iterators and using them results in undefined behavior. + * + * @note + * Groups share references to the underlying data structures of the registry + * that generated them. Therefore any change to the entities and to the + * components made by means of the registry are immediately reflected by all the + * groups. + * Moreover, sorting an owning group affects all the instance of the same group + * (it means that users don't have to call `sort` on each instance to sort all + * of them because they share the underlying data structure). + * + * @warning + * Lifetime of a group must not overcome that of the registry that generated it. + * In any other case, attempting to use a group results in undefined behavior. + * + * @tparam Entity A valid entity type (see entt_traits for more details). + * @tparam Owned Types of components owned by the group. + * @tparam Get Types of components observed by the group. + * @tparam Exclude Types of components used to filter the group. + */ +template +class basic_group, get_t, exclude_t> { + /*! @brief A registry is allowed to create groups. */ + friend class basic_registry; + + template + using storage_type = constness_as_t>::storage_type, Comp>; + + using basic_common_type = std::common_type_t::base_type..., typename storage_type::base_type...>; + + class extended_group_iterator final { + template + auto index_to_element(storage_type &cpool) const { + if constexpr(ignore_as_empty_v>) { + return std::make_tuple(); + } else { + return std::forward_as_tuple(cpool.rbegin()[it.index()]); + } + } + + public: + using difference_type = std::ptrdiff_t; + using value_type = decltype(std::tuple_cat(std::tuple{}, std::declval().get({}))); + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + extended_group_iterator() = default; + + template + extended_group_iterator(typename basic_common_type::iterator from, const std::tuple *..., storage_type *...> &cpools) + : it{from}, + pools{cpools} {} + + extended_group_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } + + extended_group_iterator operator++(int) ENTT_NOEXCEPT { + extended_group_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return std::tuple_cat( + std::make_tuple(*it), + index_to_element(*std::get *>(pools))..., + std::get *>(pools)->get_as_tuple(*it)...); + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + [[nodiscard]] bool operator==(const extended_group_iterator &other) const ENTT_NOEXCEPT { + return other.it == it; + } + + [[nodiscard]] bool operator!=(const extended_group_iterator &other) const ENTT_NOEXCEPT { + return !(*this == other); + } + + private: + typename basic_common_type::iterator it; + std::tuple *..., storage_type *...> pools; + }; + + basic_group(const std::size_t &extent, storage_type &...opool, storage_type &...gpool) ENTT_NOEXCEPT + : pools{&opool..., &gpool...}, + length{&extent} {} + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using base_type = basic_common_type; + /*! @brief Random access iterator type. */ + using iterator = typename base_type::iterator; + /*! @brief Reversed iterator type. */ + using reverse_iterator = typename base_type::reverse_iterator; + /*! @brief Iterable group type. */ + using iterable = iterable_adaptor; + + /*! @brief Default constructor to use to create empty, invalid groups. */ + basic_group() ENTT_NOEXCEPT + : length{} {} + + /** + * @brief Returns the storage for a given component type. + * @tparam Comp Type of component of which to return the storage. + * @return The storage for the given component type. + */ + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get *>(pools); + } + + /** + * @brief Returns the storage for a given component type. + * @tparam Comp Index of component of which to return the storage. + * @return The storage for the given component type. + */ + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get(pools); + } + + /** + * @brief Returns the number of entities that have the given components. + * @return Number of entities that have the given components. + */ + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return *this ? *length : size_type{}; + } + + /** + * @brief Checks whether a group is empty. + * @return True if the group is empty, false otherwise. + */ + [[nodiscard]] bool empty() const ENTT_NOEXCEPT { + return !*this || !*length; + } + + /** + * @brief Returns an iterator to the first entity of the group. + * + * The returned iterator points 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 ENTT_NOEXCEPT { + return *this ? (std::get<0>(pools)->base_type::end() - *length) : iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the group. + * + * The returned iterator points to the entity following the last entity of + * the group. Attempting to dereference the returned iterator results in + * undefined behavior. + * + * @return An iterator to the entity following the last entity of the + * group. + */ + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return *this ? std::get<0>(pools)->base_type::end() : iterator{}; + } + + /** + * @brief Returns an iterator to the first entity of the reversed group. + * + * The returned iterator points 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 ENTT_NOEXCEPT { + return *this ? std::get<0>(pools)->base_type::rbegin() : reverse_iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the reversed + * group. + * + * The returned iterator points to the entity following the last entity of + * the reversed group. Attempting to dereference the returned iterator + * results in undefined behavior. + * + * @return An iterator to the entity following the last entity of the + * reversed group. + */ + [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT { + return *this ? (std::get<0>(pools)->base_type::rbegin() + *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 ENTT_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 ENTT_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 ENTT_NOEXCEPT { + const auto it = *this ? std::get<0>(pools)->find(entt) : iterator{}; + return it != end() && it >= begin() && *it == entt ? it : end(); + } + + /** + * @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()[pos]; + } + + /** + * @brief Checks if a group is properly initialized. + * @return True if the group is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return length != 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 ENTT_NOEXCEPT { + return *this && std::get<0>(pools)->contains(entt) && (std::get<0>(pools)->index(entt) < (*length)); + } + + /** + * @brief Returns the components assigned to the given entity. + * + * Prefer this function instead of `registry::get` during iterations. It has + * far better performance than its counterpart. + * + * @warning + * Attempting to use an invalid component type results in a compilation + * error. Attempting to use an entity that doesn't belong to the group + * results in undefined behavior. + * + * @tparam Comp Types of components to get. + * @param entt A valid identifier. + * @return The components assigned to the entity. + */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + ENTT_ASSERT(contains(entt), "Group does not contain entity"); + + if constexpr(sizeof...(Comp) == 0) { + return std::tuple_cat(std::get *>(pools)->get_as_tuple(entt)..., std::get *>(pools)->get_as_tuple(entt)...); + } else if constexpr(sizeof...(Comp) == 1) { + return (std::get *>(pools)->get(entt), ...); + } else { + return std::tuple_cat(std::get *>(pools)->get_as_tuple(entt)...); + } + } + + /** + * @brief Iterates entities and components 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 components. The + * _constness_ of the components 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{}, std::declval().get({})))>) { + std::apply(func, args); + } else { + std::apply([&func](auto, auto &&...less) { func(std::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 components. The _constness_ of the + * components 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 ENTT_NOEXCEPT { + iterator last = length ? std::get<0>(pools)->basic_common_type::end() : iterator{}; + return {extended_group_iterator{last - *length, pools}, extended_group_iterator{last, pools}}; + } + + /** + * @brief Sort a group according to the given comparison function. + * + * Sort the group so that iterating it with a couple of iterators returns + * entities and components in the expected order. See `begin` and `end` for + * more details. + * + * 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(std::tuple, std::tuple); + * bool(const Component &, const Component &); + * bool(const Entity, const Entity); + * @endcode + * + * Where `Component` 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 Comp Optional types of components 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 { + auto *cpool = std::get<0>(pools); + + if constexpr(sizeof...(Comp) == 0) { + static_assert(std::is_invocable_v, "Invalid comparison function"); + cpool->sort_n(*length, std::move(compare), std::move(algo), std::forward(args)...); + } else { + auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) { + if constexpr(sizeof...(Comp) == 1) { + return compare((std::get *>(pools)->get(lhs), ...), (std::get *>(pools)->get(rhs), ...)); + } else { + return compare(std::forward_as_tuple(std::get *>(pools)->get(lhs)...), std::forward_as_tuple(std::get *>(pools)->get(rhs)...)); + } + }; + + cpool->sort_n(*length, std::move(comp), std::move(algo), std::forward(args)...); + } + + [this](auto *head, auto *...other) { + for(auto next = *length; next; --next) { + const auto pos = next - 1; + [[maybe_unused]] const auto entt = head->data()[pos]; + (other->swap_elements(other->data()[pos], entt), ...); + } + }(std::get *>(pools)...); + } + +private: + const std::tuple *..., storage_type *...> pools; + const size_type *const length; +}; + +} // namespace entt + +#endif + +// #include "runtime_view.hpp" +#ifndef ENTT_ENTITY_RUNTIME_VIEW_HPP +#define ENTT_ENTITY_RUNTIME_VIEW_HPP + +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "entity.hpp" + +// #include "fwd.hpp" + +// #include "sparse_set.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +class runtime_view_iterator final { + using iterator_type = typename Set::iterator; + + [[nodiscard]] bool valid() const { + return (!tombstone_check || *it != tombstone) + && std::all_of(++pools->begin(), pools->end(), [entt = *it](const auto *curr) { return curr->contains(entt); }) + && std::none_of(filter->cbegin(), filter->cend(), [entt = *it](const auto *curr) { return curr && curr->contains(entt); }); + } + +public: + using difference_type = typename iterator_type::difference_type; + using value_type = typename iterator_type::value_type; + using pointer = typename iterator_type::pointer; + using reference = typename iterator_type::reference; + using iterator_category = std::bidirectional_iterator_tag; + + runtime_view_iterator() ENTT_NOEXCEPT + : pools{}, + filter{}, + it{}, + tombstone_check{} {} + + runtime_view_iterator(const std::vector &cpools, const std::vector &ignore, iterator_type curr) ENTT_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++() { + while(++it != (*pools)[0]->end() && !valid()) {} + return *this; + } + + runtime_view_iterator operator++(int) { + runtime_view_iterator orig = *this; + return ++(*this), orig; + } + + runtime_view_iterator &operator--() { + while(--it != (*pools)[0]->begin() && !valid()) {} + return *this; + } + + runtime_view_iterator operator--(int) { + runtime_view_iterator orig = *this; + return operator--(), orig; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return it.operator->(); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return *operator->(); + } + + [[nodiscard]] bool operator==(const runtime_view_iterator &other) const ENTT_NOEXCEPT { + return it == other.it; + } + + [[nodiscard]] bool operator!=(const runtime_view_iterator &other) const ENTT_NOEXCEPT { + return !(*this == other); + } + +private: + const std::vector *pools; + const std::vector *filter; + iterator_type it; + bool tombstone_check; +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Runtime view implementation. + * + * Primary template isn't defined on purpose. All the specializations give a + * compile-time error, but for a few reasonable cases. + */ +template +struct basic_runtime_view; + +/** + * @brief Generic runtime view. + * + * Runtime views iterate over those entities that have at least all the given + * components in their bags. During initialization, a runtime view looks at the + * number of entities available for each component and picks up a reference to + * the smallest set of candidate entities in order to get a performance boost + * when iterate.
+ * Order of elements during iterations are highly dependent on the order of the + * underlying data structures. See sparse_set and its specializations for more + * details. + * + * @b Important + * + * Iterators aren't invalidated if: + * + * * New instances of the given components are created and assigned to entities. + * * The entity currently pointed is modified (as an example, if one of the + * given components is removed from the entity to which the iterator points). + * * The entity currently pointed is destroyed. + * + * In all the other cases, modifying the pools of the given components in any + * way invalidates all the iterators and using them results in undefined + * behavior. + * + * @note + * Views share references to the underlying data structures of the registry that + * generated them. Therefore any change to the entities and to the components + * made by means of the registry are immediately reflected by the views, unless + * a pool was missing when the view was built (in this case, the view won't + * have a valid reference and won't be updated accordingly). + * + * @warning + * Lifetime of a view must not overcome that of the registry that generated it. + * In any other case, attempting to use a view results in undefined behavior. + * + * @tparam Entity A valid entity type (see entt_traits for more details). + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +struct basic_runtime_view> { + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using base_type = basic_sparse_set; + /*! @brief Bidirectional iterator type. */ + using iterator = internal::runtime_view_iterator; + + /*! @brief Default constructor to use to create empty, invalid views. */ + basic_runtime_view() ENTT_NOEXCEPT + : pools{}, + filter{} {} + + /** + * @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(const base_type &base) { + if(pools.empty() || !(base.size() < pools[0u]->size())) { + pools.push_back(&base); + } else { + pools.push_back(std::exchange(pools[0u], &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(const base_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{} : pools.front()->size(); + } + + /** + * @brief Returns an iterator to the first entity that has the given + * components. + * + * The returned iterator points to the first entity that has the given + * components. If the view is empty, the returned iterator will be equal to + * `end()`. + * + * @return An iterator to the first entity that has the given components. + */ + [[nodiscard]] iterator begin() const { + return pools.empty() ? iterator{} : iterator{pools, filter, pools[0]->begin()}; + } + + /** + * @brief Returns an iterator that is past the last entity that has the + * given components. + * + * The returned iterator points to the entity following the last entity that + * has the given components. Attempting to dereference the returned iterator + * results in undefined behavior. + * + * @return An iterator to the entity following the last entity that has the + * given components. + */ + [[nodiscard]] iterator end() const { + return pools.empty() ? iterator{} : iterator{pools, filter, pools[0]->end()}; + } + + /** + * @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() + && std::all_of(pools.cbegin(), pools.cend(), [entt](const auto *curr) { return curr->contains(entt); }) + && std::none_of(filter.cbegin(), filter.cend(), [entt](const auto *curr) { return curr && curr->contains(entt); }); + } + + /** + * @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. To get the components, users can use the registry with + * which the view was built.
+ * 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: + std::vector pools; + std::vector filter; +}; + +} // namespace entt + +#endif + +// #include "sparse_set.hpp" + +// #include "storage.hpp" + +// #include "utility.hpp" + +// #include "view.hpp" +#ifndef ENTT_ENTITY_VIEW_HPP +#define ENTT_ENTITY_VIEW_HPP + +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/iterator.hpp" + +// #include "../core/type_traits.hpp" + +// #include "component.hpp" + +// #include "entity.hpp" + +// #include "fwd.hpp" + +// #include "sparse_set.hpp" + +// #include "storage.hpp" + +// #include "utility.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +class view_iterator final { + using iterator_type = typename Type::const_iterator; + + [[nodiscard]] bool valid() const ENTT_NOEXCEPT { + return ((Component != 0u) || (*it != tombstone)) + && std::apply([entt = *it](const auto *...curr) { return (curr->contains(entt) && ...); }, pools) + && std::apply([entt = *it](const auto *...curr) { return (!curr->contains(entt) && ...); }, filter); + } + +public: + using value_type = typename iterator_type::value_type; + using pointer = typename iterator_type::pointer; + using reference = typename iterator_type::reference; + using difference_type = typename iterator_type::difference_type; + using iterator_category = std::forward_iterator_tag; + + view_iterator() ENTT_NOEXCEPT = default; + + view_iterator(iterator_type curr, iterator_type to, std::array all_of, std::array none_of) ENTT_NOEXCEPT + : it{curr}, + last{to}, + pools{all_of}, + filter{none_of} { + if(it != last && !valid()) { + ++(*this); + } + } + + view_iterator &operator++() ENTT_NOEXCEPT { + while(++it != last && !valid()) {} + return *this; + } + + view_iterator operator++(int) ENTT_NOEXCEPT { + view_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return &*it; + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return *operator->(); + } + + template + friend bool operator==(const view_iterator &, const view_iterator &) ENTT_NOEXCEPT; + +private: + iterator_type it; + iterator_type last; + std::array pools; + std::array filter; +}; + +template +[[nodiscard]] bool operator==(const view_iterator &lhs, const view_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] bool operator!=(const view_iterator &lhs, const view_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +struct extended_view_iterator final { + using difference_type = std::ptrdiff_t; + using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval()), std::declval().get_as_tuple({})...)); + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + extended_view_iterator() = default; + + extended_view_iterator(It from, std::tuple storage) + : it{from}, + pools{storage} {} + + extended_view_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } + + extended_view_iterator operator++(int) ENTT_NOEXCEPT { + extended_view_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return std::apply([entt = *it](auto *...curr) { return std::tuple_cat(std::make_tuple(entt), curr->get_as_tuple(entt)...); }, pools); + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + template + friend bool operator==(const extended_view_iterator &, const extended_view_iterator &) ENTT_NOEXCEPT; + +private: + It it; + std::tuple pools; +}; + +template +[[nodiscard]] bool operator==(const extended_view_iterator &lhs, const extended_view_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] bool operator!=(const extended_view_iterator &lhs, const extended_view_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @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. + */ +template +class basic_view; + +/** + * @brief Multi component view. + * + * Multi component views iterate over those entities that have at least all the + * given components in their bags. During initialization, a multi component view + * looks at the number of entities available for each component and uses the + * smallest set in order to get a performance boost when iterate. + * + * @b Important + * + * Iterators aren't invalidated if: + * + * * New instances of the given components are created and assigned to entities. + * * The entity currently pointed is modified (as an example, if one of the + * given components is removed from the entity to which the iterator points). + * * The entity currently pointed is destroyed. + * + * In all other cases, modifying the pools iterated by the view in any way + * invalidates all the iterators and using them results in undefined behavior. + * + * @tparam Entity A valid entity type (see entt_traits for more details). + * @tparam Component Types of components iterated by the view. + * @tparam Exclude Types of components used to filter the view. + */ +template +class basic_view, exclude_t> { + template + friend class basic_view; + + template + using storage_type = constness_as_t>::storage_type, Comp>; + + template + [[nodiscard]] auto pools_to_array(std::index_sequence) const ENTT_NOEXCEPT { + std::size_t pos{}; + std::array other{}; + (static_cast(std::get(pools) == view ? void() : void(other[pos++] = std::get(pools))), ...); + return other; + } + + template + [[nodiscard]] auto dispatch_get(const std::tuple &curr) const { + if constexpr(Comp == Other) { + return std::forward_as_tuple(std::get(curr)...); + } else { + return std::get(pools)->get_as_tuple(std::get<0>(curr)); + } + } + + template + void each(Func func, std::index_sequence) const { + for(const auto curr: std::get(pools)->each()) { + const auto entt = std::get<0>(curr); + + if(((sizeof...(Component) != 1u) || (entt != tombstone)) + && ((Comp == Index || std::get(pools)->contains(entt)) && ...) + && std::apply([entt](const auto *...cpool) { return (!cpool->contains(entt) && ...); }, filter)) { + if constexpr(is_applicable_v{}, std::declval().get({})))>) { + std::apply(func, std::tuple_cat(std::make_tuple(entt), dispatch_get(curr)...)); + } else { + std::apply(func, std::tuple_cat(dispatch_get(curr)...)); + } + } + } + } + + template + void pick_and_each(Func func, std::index_sequence seq) const { + ((std::get(pools) == view ? each(std::move(func), seq) : void()), ...); + } + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using base_type = std::common_type_t::base_type...>; + /*! @brief Bidirectional iterator type. */ + using iterator = internal::view_iterator; + /*! @brief Iterable view type. */ + using iterable = iterable_adaptor...>>; + + /*! @brief Default constructor to use to create empty, invalid views. */ + basic_view() ENTT_NOEXCEPT + : pools{}, + filter{}, + view{} {} + + /** + * @brief Constructs a multi-type view from a set of storage classes. + * @param component The storage for the types to iterate. + * @param epool The storage for the types used to filter the view. + */ + basic_view(storage_type &...component, const storage_type &...epool) ENTT_NOEXCEPT + : pools{&component...}, + filter{&epool...}, + view{(std::min)({&static_cast(component)...}, [](auto *lhs, auto *rhs) { return lhs->size() < rhs->size(); })} {} + + /** + * @brief Creates a new view driven by a given component in its iterations. + * @tparam Comp Type of component used to drive the iteration. + * @return A new view driven by the given component in its iterations. + */ + template + [[nodiscard]] basic_view use() const ENTT_NOEXCEPT { + basic_view other{*this}; + other.view = std::get *>(pools); + return other; + } + + /** + * @brief Creates a new view driven by a given component in its iterations. + * @tparam Comp Index of the component used to drive the iteration. + * @return A new view driven by the given component in its iterations. + */ + template + [[nodiscard]] basic_view use() const ENTT_NOEXCEPT { + basic_view other{*this}; + other.view = std::get(pools); + return other; + } + + /** + * @brief Returns the leading storage of a view. + * @return The leading storage of the view. + */ + const base_type &handle() const ENTT_NOEXCEPT { + return *view; + } + + /** + * @brief Returns the storage for a given component type. + * @tparam Comp Type of component of which to return the storage. + * @return The storage for the given component type. + */ + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get *>(pools); + } + + /** + * @brief Returns the storage for a given component type. + * @tparam Comp Index of component of which to return the storage. + * @return The storage for the given component type. + */ + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get(pools); + } + + /** + * @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 ENTT_NOEXCEPT { + return view->size(); + } + + /** + * @brief Returns an iterator to the first entity of the view. + * + * The returned iterator points 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 ENTT_NOEXCEPT { + return iterator{view->begin(), view->end(), pools_to_array(std::index_sequence_for{}), filter}; + } + + /** + * @brief Returns an iterator that is past the last entity of the view. + * + * The returned iterator points to the entity following the last entity of + * the view. Attempting to dereference the returned iterator results in + * undefined behavior. + * + * @return An iterator to the entity following the last entity of the view. + */ + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return iterator{view->end(), view->end(), pools_to_array(std::index_sequence_for{}), filter}; + } + + /** + * @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 ENTT_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 ENTT_NOEXCEPT { + auto it = view->rbegin(); + for(const auto last = view->rend(); it != last && !contains(*it); ++it) {} + return it == view->rend() ? null : *it; + } + + /** + * @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 ENTT_NOEXCEPT { + return contains(entt) ? iterator{view->find(entt), view->end(), pools_to_array(std::index_sequence_for{}), filter} : end(); + } + + /** + * @brief Returns the components assigned to the given entity. + * @param entt A valid identifier. + * @return The components assigned to the given entity. + */ + [[nodiscard]] decltype(auto) operator[](const entity_type entt) const { + return get(entt); + } + + /** + * @brief Checks if a view is properly initialized. + * @return True if the view is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return view != 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 ENTT_NOEXCEPT { + return std::apply([entt](const auto *...curr) { return (curr->contains(entt) && ...); }, pools) + && std::apply([entt](const auto *...curr) { return (!curr->contains(entt) && ...); }, filter); + } + + /** + * @brief Returns the components assigned to the given entity. + * + * @warning + * Attempting to use an entity that doesn't belong to the view results in + * undefined behavior. + * + * @tparam Comp Types of components to get. + * @param entt A valid identifier. + * @return The components assigned to the entity. + */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + ENTT_ASSERT(contains(entt), "View does not contain entity"); + + if constexpr(sizeof...(Comp) == 0) { + return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, pools); + } else if constexpr(sizeof...(Comp) == 1) { + return (std::get *>(pools)->get(entt), ...); + } else { + return std::tuple_cat(std::get *>(pools)->get_as_tuple(entt)...); + } + } + + /** + * @brief Returns the components assigned to the given entity. + * + * @warning + * Attempting to use an entity that doesn't belong to the view results in + * undefined behavior. + * + * @tparam First Index of a component to get. + * @tparam Other Indexes of other components to get. + * @param entt A valid identifier. + * @return The components assigned to the entity. + */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + ENTT_ASSERT(contains(entt), "View does not contain entity"); + + if constexpr(sizeof...(Other) == 0) { + return std::get(pools)->get(entt); + } else { + return std::tuple_cat(std::get(pools)->get_as_tuple(entt), std::get(pools)->get_as_tuple(entt)...); + } + } + + /** + * @brief Iterates entities and components 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 components. The + * _constness_ of the components 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 + * + * @tparam Func Type of the function object to invoke. + * @param func A valid function object. + */ + template + void each(Func func) const { + pick_and_each(std::move(func), std::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 components. The _constness_ of the + * components is as requested. + * + * @return An iterable object to use to _visit_ the view. + */ + [[nodiscard]] iterable each() const ENTT_NOEXCEPT { + return {internal::extended_view_iterator{begin(), pools}, internal::extended_view_iterator{end(), pools}}; + } + + /** + * @brief Combines two views in a _more specific_ one (friend function). + * @tparam Get Component list of the view to combine with. + * @tparam Excl Filter list of the view to combine with. + * @param other The view to combine with. + * @return A more specific view. + */ + template + [[nodiscard]] auto operator|(const basic_view, exclude_t> &other) const ENTT_NOEXCEPT { + using view_type = basic_view, exclude_t>; + return std::make_from_tuple(std::tuple_cat( + std::apply([](auto *...curr) { return std::forward_as_tuple(*curr...); }, pools), + std::apply([](auto *...curr) { return std::forward_as_tuple(*curr...); }, other.pools), + std::apply([](const auto *...curr) { return std::forward_as_tuple(static_cast &>(*curr)...); }, filter), + std::apply([](const auto *...curr) { return std::forward_as_tuple(static_cast &>(*curr)...); }, other.filter))); + } + +private: + std::tuple *...> pools; + std::array filter; + const base_type *view; +}; + +/** + * @brief Single component view specialization. + * + * Single component views are specialized in order to get a boost in terms of + * performance. This kind of views can access the underlying data structure + * directly and avoid superfluous checks. + * + * @b Important + * + * Iterators aren't invalidated if: + * + * * New instances of the given component are created and assigned to entities. + * * The entity currently pointed is modified (as an example, the given + * component is removed from the entity to which the iterator points). + * * The entity currently pointed is destroyed. + * + * In all other cases, modifying the pool iterated by the view in any way + * invalidates all the iterators and using them results in undefined behavior. + * + * @tparam Entity A valid entity type (see entt_traits for more details). + * @tparam Component Type of component iterated by the view. + */ +template +class basic_view, exclude_t<>, std::void_t>::in_place_delete>>> { + template + friend class basic_view; + + using storage_type = constness_as_t>::storage_type, Component>; + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using base_type = typename storage_type::base_type; + /*! @brief Random access iterator type. */ + using iterator = typename base_type::iterator; + /*! @brief Reversed iterator type. */ + using reverse_iterator = typename base_type::reverse_iterator; + /*! @brief Iterable view type. */ + using iterable = decltype(std::declval().each()); + + /*! @brief Default constructor to use to create empty, invalid views. */ + basic_view() ENTT_NOEXCEPT + : pools{}, + filter{}, + view{} {} + + /** + * @brief Constructs a single-type view from a storage class. + * @param ref The storage for the type to iterate. + */ + basic_view(storage_type &ref) ENTT_NOEXCEPT + : pools{&ref}, + filter{}, + view{&ref} {} + + /** + * @brief Returns the leading storage of a view. + * @return The leading storage of the view. + */ + const base_type &handle() const ENTT_NOEXCEPT { + return *view; + } + + /** + * @brief Returns the storage for a given component type. + * @tparam Comp Type of component of which to return the storage. + * @return The storage for the given component type. + */ + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + static_assert(std::is_same_v, "Invalid component type"); + return *std::get<0>(pools); + } + + /** + * @brief Returns the storage for a given component type. + * @tparam Comp Index of component of which to return the storage. + * @return The storage for the given component type. + */ + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get(pools); + } + + /** + * @brief Returns the number of entities that have the given component. + * @return Number of entities that have the given component. + */ + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return view->size(); + } + + /** + * @brief Checks whether a view is empty. + * @return True if the view is empty, false otherwise. + */ + [[nodiscard]] bool empty() const ENTT_NOEXCEPT { + return view->empty(); + } + + /** + * @brief Returns an iterator to the first entity of the view. + * + * The returned iterator points 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 ENTT_NOEXCEPT { + return view->begin(); + } + + /** + * @brief Returns an iterator that is past the last entity of the view. + * + * The returned iterator points to the entity following the last entity of + * the view. Attempting to dereference the returned iterator results in + * undefined behavior. + * + * @return An iterator to the entity following the last entity of the view. + */ + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return view->end(); + } + + /** + * @brief Returns an iterator to the first entity of the reversed view. + * + * The returned iterator points 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 ENTT_NOEXCEPT { + return view->rbegin(); + } + + /** + * @brief Returns an iterator that is past the last entity of the reversed + * view. + * + * The returned iterator points to the entity following the last entity of + * the reversed view. Attempting to dereference the returned iterator + * results in undefined behavior. + * + * @return An iterator to the entity following the last entity of the + * reversed view. + */ + [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT { + return view->rend(); + } + + /** + * @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 ENTT_NOEXCEPT { + return empty() ? null : *begin(); + } + + /** + * @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 ENTT_NOEXCEPT { + return empty() ? null : *rbegin(); + } + + /** + * @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 ENTT_NOEXCEPT { + return contains(entt) ? view->find(entt) : end(); + } + + /** + * @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()[pos]; + } + + /** + * @brief Returns the component assigned to the given entity. + * @param entt A valid identifier. + * @return The component assigned to the given entity. + */ + [[nodiscard]] decltype(auto) operator[](const entity_type entt) const { + return get(entt); + } + + /** + * @brief Checks if a view is properly initialized. + * @return True if the view is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return view != 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 ENTT_NOEXCEPT { + return view->contains(entt); + } + + /** + * @brief Returns the component assigned to the given entity. + * + * @warning + * Attempting to use an entity that doesn't belong to the view results in + * undefined behavior. + * + * @tparam Comp Type or index of the component to get. + * @param entt A valid identifier. + * @return The component assigned to the entity. + */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + ENTT_ASSERT(contains(entt), "View does not contain entity"); + + if constexpr(sizeof...(Comp) == 0) { + return std::get<0>(pools)->get_as_tuple(entt); + } else { + static_assert(std::is_same_v, "Invalid component type"); + return std::get<0>(pools)->get(entt); + } + } + + /*! @copydoc get */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + ENTT_ASSERT(contains(entt), "View does not contain entity"); + return std::get<0>(pools)->get(entt); + } + + /** + * @brief Iterates entities and components 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 reference to the component if it's a non-empty one. + * The _constness_ of the component is as requested.
+ * The signature of the function must be equivalent to one of the following + * forms: + * + * @code{.cpp} + * void(const entity_type, Component &); + * void(Component &); + * @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) { + for(const auto pack: each()) { + std::apply(func, pack); + } + } else if constexpr(std::is_invocable_v) { + for(auto &&component: *std::get<0>(pools)) { + func(component); + } + } else if constexpr(std::is_invocable_v) { + for(auto entity: *view) { + func(entity); + } + } else { + for(size_type pos{}, last = size(); pos < last; ++pos) { + func(); + } + } + } + + /** + * @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 component if it's a non-empty one. The _constness_ of + * the component is as requested. + * + * @return An iterable object to use to _visit_ the view. + */ + [[nodiscard]] iterable each() const ENTT_NOEXCEPT { + return std::get<0>(pools)->each(); + } + + /** + * @brief Combines two views in a _more specific_ one (friend function). + * @tparam Get Component list of the view to combine with. + * @tparam Excl Filter list of the view to combine with. + * @param other The view to combine with. + * @return A more specific view. + */ + template + [[nodiscard]] auto operator|(const basic_view, exclude_t> &other) const ENTT_NOEXCEPT { + using view_type = basic_view, exclude_t>; + return std::make_from_tuple(std::tuple_cat( + std::forward_as_tuple(*std::get<0>(pools)), + std::apply([](auto *...curr) { return std::forward_as_tuple(*curr...); }, other.pools), + std::apply([](const auto *...curr) { return std::forward_as_tuple(static_cast &>(*curr)...); }, other.filter))); + } + +private: + std::tuple pools; + std::array filter; + const base_type *view; +}; + +/** + * @brief Deduction guide. + * @tparam Storage Type of storage classes used to create the view. + * @param storage The storage for the types to iterate. + */ +template +basic_view(Storage &...storage) -> basic_view, get_t...>, exclude_t<>>; + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +class storage_proxy_iterator final { + template + friend class storage_proxy_iterator; + + using mapped_type = std::remove_reference_t()->second)>; + +public: + using value_type = std::pair &>; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + storage_proxy_iterator() ENTT_NOEXCEPT + : it{} {} + + storage_proxy_iterator(const It iter) ENTT_NOEXCEPT + : it{iter} {} + + template && std::is_constructible_v>> + storage_proxy_iterator(const storage_proxy_iterator &other) ENTT_NOEXCEPT + : it{other.it} {} + + storage_proxy_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } + + storage_proxy_iterator operator++(int) ENTT_NOEXCEPT { + storage_proxy_iterator orig = *this; + return ++(*this), orig; + } + + storage_proxy_iterator &operator--() ENTT_NOEXCEPT { + return --it, *this; + } + + storage_proxy_iterator operator--(int) ENTT_NOEXCEPT { + storage_proxy_iterator orig = *this; + return operator--(), orig; + } + + storage_proxy_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + it += value; + return *this; + } + + storage_proxy_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + storage_proxy_iterator copy = *this; + return (copy += value); + } + + storage_proxy_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); + } + + storage_proxy_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); + } + + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + return {it[value].first, *it[value].second}; + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return {it->first, *it->second}; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + template + friend std::ptrdiff_t operator-(const storage_proxy_iterator &, const storage_proxy_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator==(const storage_proxy_iterator &, const storage_proxy_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator<(const storage_proxy_iterator &, const storage_proxy_iterator &) ENTT_NOEXCEPT; + +private: + It it; +}; + +template +[[nodiscard]] std::ptrdiff_t operator-(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it - rhs.it; +} + +template +[[nodiscard]] bool operator==(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] bool operator!=(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +[[nodiscard]] bool operator<(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it < rhs.it; +} + +template +[[nodiscard]] bool operator>(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +template +[[nodiscard]] bool operator<=(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +template +[[nodiscard]] bool operator>=(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +struct registry_context { + template + Type &emplace_hint(const id_type id, Args &&...args) { + return any_cast(data.try_emplace(id, std::in_place_type, std::forward(args)...).first->second); + } + + template + Type &emplace(Args &&...args) { + return emplace_hint(type_id().hash(), std::forward(args)...); + } + + template + bool erase(const id_type id = type_id().hash()) { + const auto it = data.find(id); + return it != data.end() && it->second.type() == type_id() ? (data.erase(it), true) : false; + } + + template + [[nodiscard]] std::add_const_t &at(const id_type id = type_id().hash()) const { + return any_cast &>(data.at(id)); + } + + template + [[nodiscard]] Type &at(const id_type id = type_id().hash()) { + return any_cast(data.at(id)); + } + + template + [[nodiscard]] std::add_const_t *find(const id_type id = type_id().hash()) const { + const auto it = data.find(id); + return it != data.cend() ? any_cast>(&it->second) : nullptr; + } + + template + [[nodiscard]] Type *find(const id_type id = type_id().hash()) { + const auto it = data.find(id); + return it != data.end() ? any_cast(&it->second) : nullptr; + } + + template + [[nodiscard]] bool contains(const id_type id = type_id().hash()) const { + const auto it = data.find(id); + return it != data.end() && it->second.type() == type_id(); + } + +private: + dense_map, identity> data; +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Fast and reliable entity-component system. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +class basic_registry { + using entity_traits = entt_traits; + using basic_common_type = basic_sparse_set; + + template + using storage_type = typename storage_traits::storage_type; + + template + struct group_handler; + + template + struct group_handler, get_t, Owned...> { + // nasty workaround for an issue with the toolset v141 that doesn't accept a fold expression here + static_assert(!std::disjunction_v::in_place_delete>...>, "Groups do not support in-place delete"); + std::conditional_t current{}; + + template + void maybe_valid_if(basic_registry &owner, const Entity entt) { + [[maybe_unused]] const auto cpools = std::forward_as_tuple(owner.assure()...); + + const auto is_valid = ((std::is_same_v || std::get &>(cpools).contains(entt)) && ...) + && ((std::is_same_v || owner.assure().contains(entt)) && ...) + && ((std::is_same_v || !owner.assure().contains(entt)) && ...); + + if constexpr(sizeof...(Owned) == 0) { + if(is_valid && !current.contains(entt)) { + current.emplace(entt); + } + } else { + if(is_valid && !(std::get<0>(cpools).index(entt) < current)) { + const auto pos = current++; + (std::get &>(cpools).swap_elements(std::get &>(cpools).data()[pos], entt), ...); + } + } + } + + void discard_if([[maybe_unused]] basic_registry &owner, const Entity entt) { + if constexpr(sizeof...(Owned) == 0) { + current.remove(entt); + } else { + if(const auto cpools = std::forward_as_tuple(owner.assure()...); std::get<0>(cpools).contains(entt) && (std::get<0>(cpools).index(entt) < current)) { + const auto pos = --current; + (std::get &>(cpools).swap_elements(std::get &>(cpools).data()[pos], entt), ...); + } + } + } + }; + + struct group_data { + std::size_t size; + std::unique_ptr group; + bool (*owned)(const id_type) ENTT_NOEXCEPT; + bool (*get)(const id_type) ENTT_NOEXCEPT; + bool (*exclude)(const id_type) ENTT_NOEXCEPT; + }; + + template + [[nodiscard]] auto &assure(const id_type id = type_hash::value()) { + static_assert(std::is_same_v>, "Non-decayed types not allowed"); + auto &&cpool = pools[id]; + + if(!cpool) { + cpool.reset(new storage_type{}); + cpool->bind(forward_as_any(*this)); + } + + ENTT_ASSERT(cpool->type() == type_id(), "Unexpected type"); + return static_cast &>(*cpool); + } + + template + [[nodiscard]] const auto &assure(const id_type id = type_hash::value()) const { + static_assert(std::is_same_v>, "Non-decayed types not allowed"); + + if(const auto it = pools.find(id); it != pools.cend()) { + ENTT_ASSERT(it->second->type() == type_id(), "Unexpected type"); + return static_cast &>(*it->second); + } + + static storage_type placeholder{}; + return placeholder; + } + + auto generate_identifier(const std::size_t pos) ENTT_NOEXCEPT { + ENTT_ASSERT(pos < entity_traits::to_entity(null), "No entities available"); + return entity_traits::combine(static_cast(pos), {}); + } + + auto recycle_identifier() ENTT_NOEXCEPT { + ENTT_ASSERT(free_list != null, "No entities available"); + const auto curr = entity_traits::to_entity(free_list); + free_list = entity_traits::combine(entity_traits::to_integral(entities[curr]), tombstone); + return (entities[curr] = entity_traits::combine(curr, entity_traits::to_integral(entities[curr]))); + } + + auto release_entity(const Entity entity, const typename entity_traits::version_type version) { + const typename entity_traits::version_type vers = version + (version == entity_traits::to_version(tombstone)); + entities[entity_traits::to_entity(entity)] = entity_traits::construct(entity_traits::to_integral(free_list), vers); + free_list = entity_traits::combine(entity_traits::to_integral(entity), tombstone); + return vers; + } + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Underlying version type. */ + using version_type = typename entity_traits::version_type; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using base_type = basic_common_type; + /*! @brief Context type. */ + using context = internal::registry_context; + + /*! @brief Default constructor. */ + basic_registry() + : pools{}, + groups{}, + entities{}, + free_list{tombstone}, + vars{} {} + + /** + * @brief Allocates enough memory upon construction to store `count` pools. + * @param count The number of pools to allocate memory for. + */ + basic_registry(const size_type count) + : pools{}, + groups{}, + entities{}, + free_list{tombstone}, + vars{} { + pools.reserve(count); + } + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_registry(basic_registry &&other) + : pools{std::move(other.pools)}, + groups{std::move(other.groups)}, + entities{std::move(other.entities)}, + free_list{other.free_list}, + vars{std::move(other.vars)} { + for(auto &&curr: pools) { + curr.second->bind(forward_as_any(*this)); + } + } + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This registry. + */ + basic_registry &operator=(basic_registry &&other) { + pools = std::move(other.pools); + groups = std::move(other.groups); + entities = std::move(other.entities); + free_list = other.free_list; + vars = std::move(other.vars); + + for(auto &&curr: pools) { + curr.second->bind(forward_as_any(*this)); + } + + return *this; + } + + /** + * @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]] auto storage() ENTT_NOEXCEPT { + return iterable_adaptor{internal::storage_proxy_iterator{pools.begin()}, internal::storage_proxy_iterator{pools.end()}}; + } + + /*! @copydoc storage */ + [[nodiscard]] auto storage() const ENTT_NOEXCEPT { + return iterable_adaptor{internal::storage_proxy_iterator{pools.cbegin()}, internal::storage_proxy_iterator{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 An iterator to the given storage if it's found, past the end + * iterator otherwise. + */ + [[nodiscard]] auto storage(const id_type id) { + return internal::storage_proxy_iterator{pools.find(id)}; + } + + /** + * @brief Finds the storage associated with a given name, if any. + * @param id Name used to map the storage within the registry. + * @return An iterator to the given storage if it's found, past the end + * iterator otherwise. + */ + [[nodiscard]] auto storage(const id_type id) const { + return internal::storage_proxy_iterator{pools.find(id)}; + } + + /** + * @brief Returns the storage for a given component type. + * @tparam Component Type of component of which to return the storage. + * @param id Optional name used to map the storage within the registry. + * @return The storage for the given component type. + */ + template + decltype(auto) storage(const id_type id = type_hash>::value()) { + if constexpr(std::is_const_v) { + return std::as_const(*this).template storage>(id); + } else { + return assure(id); + } + } + + /** + * @brief Returns the storage for a given component type. + * + * @warning + * If a storage for the given component doesn't exist yet, a temporary + * placeholder is returned instead. + * + * @tparam Component Type of component of which to return the storage. + * @param id Optional name used to map the storage within the registry. + * @return The storage for the given component type. + */ + template + decltype(auto) storage(const id_type id = type_hash>::value()) const { + return assure>(id); + } + + /** + * @brief Returns the number of entities created so far. + * @return Number of entities created so far. + */ + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return entities.size(); + } + + /** + * @brief Returns the number of entities still in use. + * @return Number of entities still in use. + */ + [[nodiscard]] size_type alive() const { + auto sz = entities.size(); + + for(auto curr = free_list; curr != null; --sz) { + curr = entities[entity_traits::to_entity(curr)]; + } + + return sz; + } + + /** + * @brief Increases the capacity (number of entities) of the registry. + * @param cap Desired capacity. + */ + void reserve(const size_type cap) { + entities.reserve(cap); + } + + /** + * @brief Returns the number of entities that a registry has currently + * allocated space for. + * @return Capacity of the registry. + */ + [[nodiscard]] size_type capacity() const ENTT_NOEXCEPT { + return entities.capacity(); + } + + /** + * @brief Checks whether the registry is empty (no entities still in use). + * @return True if the registry is empty, false otherwise. + */ + [[nodiscard]] bool empty() const { + return !alive(); + } + + /** + * @brief Direct access to the list of entities of a registry. + * + * The returned pointer is such that range `[data(), data() + size())` is + * always a valid range, even if the registry is empty. + * + * @warning + * This list contains both valid and destroyed entities and isn't suitable + * for direct use. + * + * @return A pointer to the array of entities. + */ + [[nodiscard]] const entity_type *data() const ENTT_NOEXCEPT { + return entities.data(); + } + + /** + * @brief Returns the head of the list of released entities. + * + * This function is intended for use in conjunction with `assign`.
+ * The returned entity has an invalid identifier in all cases. + * + * @return The head of the list of released entities. + */ + [[nodiscard]] entity_type released() const ENTT_NOEXCEPT { + return free_list; + } + + /** + * @brief Checks if an identifier refers to a valid entity. + * @param entity An identifier, either valid or not. + * @return True if the identifier is valid, false otherwise. + */ + [[nodiscard]] bool valid(const entity_type entity) const { + const auto pos = size_type(entity_traits::to_entity(entity)); + return (pos < entities.size() && entities[pos] == entity); + } + + /** + * @brief Returns the actual version for an identifier. + * @param entity A valid identifier. + * @return The version for the given identifier if valid, the tombstone + * version otherwise. + */ + [[nodiscard]] version_type current(const entity_type entity) const { + const auto pos = size_type(entity_traits::to_entity(entity)); + return entity_traits::to_version(pos < entities.size() ? entities[pos] : tombstone); + } + + /** + * @brief Creates a new entity or recycles a destroyed one. + * @return A valid identifier. + */ + [[nodiscard]] entity_type create() { + return (free_list == null) ? entities.emplace_back(generate_identifier(entities.size())) : recycle_identifier(); + } + + /** + * @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) { + const auto length = entities.size(); + + if(hint == null || hint == tombstone) { + return create(); + } else if(const auto req = entity_traits::to_entity(hint); !(req < length)) { + entities.resize(size_type(req) + 1u, null); + + for(auto pos = length; pos < req; ++pos) { + release_entity(generate_identifier(pos), {}); + } + + return (entities[req] = hint); + } else if(const auto curr = entity_traits::to_entity(entities[req]); req == curr) { + return create(); + } else { + auto *it = &free_list; + for(; entity_traits::to_entity(*it) != req; it = &entities[entity_traits::to_entity(*it)]) {} + *it = entity_traits::combine(curr, entity_traits::to_integral(*it)); + return (entities[req] = hint); + } + } + + /** + * @brief Assigns each element in a range an identifier. + * + * @sa create + * + * @tparam It Type of forward 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 + void create(It first, It last) { + for(; free_list != null && first != last; ++first) { + *first = recycle_identifier(); + } + + const auto length = entities.size(); + entities.resize(length + std::distance(first, last), null); + + for(auto pos = length; first != last; ++first, ++pos) { + *first = entities[pos] = generate_identifier(pos); + } + } + + /** + * @brief Assigns identifiers to an empty registry. + * + * This function is intended for use in conjunction with `data`, `size` and + * `destroyed`.
+ * Don't try to inject ranges of randomly generated entities nor the _wrong_ + * head for the list of destroyed entities. There is no guarantee that a + * registry will continue to work properly in this case. + * + * @warning + * There must be no entities still alive for this to work properly. + * + * @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 destroyed The head of the list of destroyed entities. + */ + template + void assign(It first, It last, const entity_type destroyed) { + ENTT_ASSERT(!alive(), "Entities still alive"); + entities.assign(first, last); + free_list = destroyed; + } + + /** + * @brief Releases an identifier. + * + * The version is updated and the identifier can be recycled at any time. + * + * @warning + * Attempting to use an invalid entity results in undefined behavior. + * + * @param entity A valid identifier. + * @return The version of the recycled entity. + */ + version_type release(const entity_type entity) { + return release(entity, static_cast(entity_traits::to_version(entity) + 1u)); + } + + /** + * @brief Releases an identifier. + * + * The suggested version or the valid version closest to the suggested one + * is used instead of the implicitly generated version. + * + * @sa release + * + * @param entity A valid identifier. + * @param version A desired version upon destruction. + * @return The version actually assigned to the entity. + */ + version_type release(const entity_type entity, const version_type version) { + ENTT_ASSERT(orphan(entity), "Non-orphan entity"); + return release_entity(entity, version); + } + + /** + * @brief Releases all identifiers in a range. + * + * @sa release + * + * @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 release(It first, It last) { + for(; first != last; ++first) { + release(*first); + } + } + + /** + * @brief Destroys an entity and releases its identifier. + * + * @sa release + * + * @warning + * Adding or removing components to an entity that is being destroyed can + * result in undefined behavior. Attempting to use an invalid entity results + * in undefined behavior. + * + * @param entity A valid identifier. + * @return The version of the recycled entity. + */ + version_type destroy(const entity_type entity) { + return destroy(entity, static_cast(entity_traits::to_version(entity) + 1u)); + } + + /** + * @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 entity A valid identifier. + * @param version A desired version upon destruction. + * @return The version actually assigned to the entity. + */ + version_type destroy(const entity_type entity, const version_type version) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + + for(size_type pos = pools.size(); pos; --pos) { + pools.begin()[pos - 1u].second->remove(entity); + } + + return release_entity(entity, version); + } + + /** + * @brief Destroys all entities in a range and releases their identifiers. + * + * @sa destroy + * + * @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 destroy(It first, It last) { + for(; first != last; ++first) { + destroy(*first); + } + } + + /** + * @brief Assigns the given component to an entity. + * + * The component must have a proper constructor or be of aggregate type. + * + * @warning + * Attempting to use an invalid entity or to assign a component to an entity + * that already owns it results in undefined behavior. + * + * @tparam Component Type of component to create. + * @tparam Args Types of arguments to use to construct the component. + * @param entity A valid identifier. + * @param args Parameters to use to initialize the component. + * @return A reference to the newly created component. + */ + template + decltype(auto) emplace(const entity_type entity, Args &&...args) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return assure().emplace(entity, std::forward(args)...); + } + + /** + * @brief Assigns each entity in a range the given component. + * + * @sa emplace + * + * @tparam Component Type of component to create. + * @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 value An instance of the component to assign. + */ + template + void insert(It first, It last, const Component &value = {}) { + ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity"); + assure().insert(first, last, value); + } + + /** + * @brief Assigns each entity in a range the given components. + * + * @sa emplace + * + * @tparam Component Type of component 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 components. + */ + template::value_type, Component>>> + void insert(EIt first, EIt last, CIt from) { + ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity"); + assure().insert(first, last, from); + } + + /** + * @brief Assigns or replaces the given component for an entity. + * + * @warning + * Attempting to use an invalid entity results in undefined behavior. + * + * @tparam Component Type of component to assign or replace. + * @tparam Args Types of arguments to use to construct the component. + * @param entity A valid identifier. + * @param args Parameters to use to initialize the component. + * @return A reference to the newly created component. + */ + template + decltype(auto) emplace_or_replace(const entity_type entity, Args &&...args) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + auto &cpool = assure(); + + return cpool.contains(entity) + ? cpool.patch(entity, [&args...](auto &...curr) { ((curr = Component{std::forward(args)...}), ...); }) + : cpool.emplace(entity, std::forward(args)...); + } + + /** + * @brief Patches the given component for an entity. + * + * The signature of the function should be equivalent to the following: + * + * @code{.cpp} + * void(Component &); + * @endcode + * + * @note + * Empty types aren't explicitly instantiated and therefore they are never + * returned. However, this function can be used to trigger an update signal + * for them. + * + * @warning + * Attempting to use an invalid entity or to patch a component of an entity + * that doesn't own it results in undefined behavior. + * + * @tparam Component Type of component to patch. + * @tparam Func Types of the function objects to invoke. + * @param entity A valid identifier. + * @param func Valid function objects. + * @return A reference to the patched component. + */ + template + decltype(auto) patch(const entity_type entity, Func &&...func) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return assure().patch(entity, std::forward(func)...); + } + + /** + * @brief Replaces the given component for an entity. + * + * The component must have a proper constructor or be of aggregate type. + * + * @warning + * Attempting to use an invalid entity or to replace a component of an + * entity that doesn't own it results in undefined behavior. + * + * @tparam Component Type of component to replace. + * @tparam Args Types of arguments to use to construct the component. + * @param entity A valid identifier. + * @param args Parameters to use to initialize the component. + * @return A reference to the component being replaced. + */ + template + decltype(auto) replace(const entity_type entity, Args &&...args) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return assure().patch(entity, [&args...](auto &...curr) { ((curr = Component{std::forward(args)...}), ...); }); + } + + /** + * @brief Removes the given components from an entity. + * + * @warning + * Attempting to use an invalid entity results in undefined behavior. + * + * @tparam Component Type of component to remove. + * @tparam Other Other types of components to remove. + * @param entity A valid identifier. + * @return The number of components actually removed. + */ + template + size_type remove(const entity_type entity) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return (assure().remove(entity) + ... + assure().remove(entity)); + } + + /** + * @brief Removes the given components from all the entities in a range. + * + * @sa remove + * + * @tparam Component Type of component to remove. + * @tparam Other Other types of components 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 components actually removed. + */ + template + size_type remove(It first, It last) { + if constexpr(sizeof...(Other) == 0u) { + ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity"); + return assure().remove(std::move(first), std::move(last)); + } else { + size_type count{}; + + for(auto cpools = std::forward_as_tuple(assure(), assure()...); first != last; ++first) { + ENTT_ASSERT(valid(*first), "Invalid entity"); + count += std::apply([entt = *first](auto &...curr) { return (curr.remove(entt) + ... + 0u); }, cpools); + } + + return count; + } + } + + /** + * @brief Erases the given components from an entity. + * + * @warning + * Attempting to use an invalid entity or to erase a component from an + * entity that doesn't own it results in undefined behavior. + * + * @tparam Component Types of components to erase. + * @tparam Other Other types of components to erase. + * @param entity A valid identifier. + */ + template + void erase(const entity_type entity) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + (assure().erase(entity), (assure().erase(entity), ...)); + } + + /** + * @brief Erases the given components from all the entities in a range. + * + * @sa erase + * + * @tparam Component Types of components to erase. + * @tparam Other Other types of components 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(sizeof...(Other) == 0u) { + ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity"); + assure().erase(std::move(first), std::move(last)); + } else { + for(auto cpools = std::forward_as_tuple(assure(), assure()...); first != last; ++first) { + ENTT_ASSERT(valid(*first), "Invalid entity"); + std::apply([entt = *first](auto &...curr) { (curr.erase(entt), ...); }, cpools); + } + } + } + + /** + * @brief Removes all tombstones from a registry or only the pools for the + * given components. + * @tparam Component Types of components for which to clear all tombstones. + */ + template + void compact() { + if constexpr(sizeof...(Component) == 0) { + for(auto &&curr: pools) { + curr.second->compact(); + } + } else { + (assure().compact(), ...); + } + } + + /** + * @brief Checks if an entity has all the given components. + * + * @warning + * Attempting to use an invalid entity results in undefined behavior. + * + * @tparam Component Components for which to perform the check. + * @param entity A valid identifier. + * @return True if the entity has all the components, false otherwise. + */ + template + [[nodiscard]] bool all_of(const entity_type entity) const { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return (assure>().contains(entity) && ...); + } + + /** + * @brief Checks if an entity has at least one of the given components. + * + * @warning + * Attempting to use an invalid entity results in undefined behavior. + * + * @tparam Component Components for which to perform the check. + * @param entity A valid identifier. + * @return True if the entity has at least one of the given components, + * false otherwise. + */ + template + [[nodiscard]] bool any_of(const entity_type entity) const { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return (assure>().contains(entity) || ...); + } + + /** + * @brief Returns references to the given components for an entity. + * + * @warning + * Attempting to use an invalid entity or to get a component from an entity + * that doesn't own it results in undefined behavior. + * + * @tparam Component Types of components to get. + * @param entity A valid identifier. + * @return References to the components owned by the entity. + */ + template + [[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entity) const { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return view().template get(entity); + } + + /*! @copydoc get */ + template + [[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entity) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return view().template get(entity); + } + + /** + * @brief Returns a reference to the given component for an entity. + * + * In case the entity doesn't own the component, the parameters provided are + * used to construct it. + * + * @warning + * Attempting to use an invalid entity results in undefined behavior. + * + * @tparam Component Type of component to get. + * @tparam Args Types of arguments to use to construct the component. + * @param entity A valid identifier. + * @param args Parameters to use to initialize the component. + * @return Reference to the component owned by the entity. + */ + template + [[nodiscard]] decltype(auto) get_or_emplace(const entity_type entity, Args &&...args) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + auto &cpool = assure(); + return cpool.contains(entity) ? cpool.get(entity) : cpool.emplace(entity, std::forward(args)...); + } + + /** + * @brief Returns pointers to the given components for an entity. + * + * @warning + * Attempting to use an invalid entity results in undefined behavior. + * + * @note + * The registry retains ownership of the pointed-to components. + * + * @tparam Component Types of components to get. + * @param entity A valid identifier. + * @return Pointers to the components owned by the entity. + */ + template + [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entity) const { + ENTT_ASSERT(valid(entity), "Invalid entity"); + + if constexpr(sizeof...(Component) == 1) { + const auto &cpool = assure...>(); + return cpool.contains(entity) ? std::addressof(cpool.get(entity)) : nullptr; + } else { + return std::make_tuple(try_get(entity)...); + } + } + + /*! @copydoc try_get */ + template + [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entity) { + if constexpr(sizeof...(Component) == 1) { + return (const_cast(std::as_const(*this).template try_get(entity)), ...); + } else { + return std::make_tuple(try_get(entity)...); + } + } + + /** + * @brief Clears a whole registry or the pools for the given components. + * @tparam Component Types of components to remove from their entities. + */ + template + void clear() { + if constexpr(sizeof...(Component) == 0) { + for(auto &&curr: pools) { + curr.second->clear(); + } + + each([this](const auto entity) { this->release(entity); }); + } else { + (assure().clear(), ...); + } + } + + /** + * @brief Iterates all the entities that are still in use. + * + * The signature of the function should be equivalent to the following: + * + * @code{.cpp} + * void(const Entity); + * @endcode + * + * It's not defined whether entities created during iteration are returned. + * + * @tparam Func Type of the function object to invoke. + * @param func A valid function object. + */ + template + void each(Func func) const { + if(free_list == null) { + for(auto pos = entities.size(); pos; --pos) { + func(entities[pos - 1]); + } + } else { + for(auto pos = entities.size(); pos; --pos) { + if(const auto entity = entities[pos - 1]; entity_traits::to_entity(entity) == (pos - 1)) { + func(entity); + } + } + } + } + + /** + * @brief Checks if an entity has components assigned. + * @param entity A valid identifier. + * @return True if the entity has no components assigned, false otherwise. + */ + [[nodiscard]] bool orphan(const entity_type entity) const { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return std::none_of(pools.cbegin(), pools.cend(), [entity](auto &&curr) { return curr.second->contains(entity); }); + } + + /** + * @brief Returns a sink object for the given component. + * + * Use this function to receive notifications whenever a new instance of the + * given component 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 component to the entity. + * + * @sa sink + * + * @tparam Component Type of component of which to get the sink. + * @return A temporary sink object. + */ + template + [[nodiscard]] auto on_construct() { + return assure().on_construct(); + } + + /** + * @brief Returns a sink object for the given component. + * + * Use this function to receive notifications whenever an instance of the + * given component 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 component. + * + * @sa sink + * + * @tparam Component Type of component of which to get the sink. + * @return A temporary sink object. + */ + template + [[nodiscard]] auto on_update() { + return assure().on_update(); + } + + /** + * @brief Returns a sink object for the given component. + * + * Use this function to receive notifications whenever an instance of the + * given component 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 component from the entity. + * + * @sa sink + * + * @tparam Component Type of component of which to get the sink. + * @return A temporary sink object. + */ + template + [[nodiscard]] auto on_destroy() { + return assure().on_destroy(); + } + + /** + * @brief Returns a view for the given components. + * + * Views are created on the fly and share with the registry its internal + * data structures. Feel free to discard them after the use.
+ * Creating and destroying a view is an incredibly cheap operation. As a + * rule of thumb, storing a view should never be an option. + * + * @tparam Component Type of component used to construct the view. + * @tparam Other Other types of components used to construct the view. + * @tparam Exclude Types of components used to filter the view. + * @return A newly created view. + */ + template + [[nodiscard]] basic_view, std::add_const_t...>, exclude_t> view(exclude_t = {}) const { + return {assure>(), assure>()..., assure()...}; + } + + /*! @copydoc view */ + template + [[nodiscard]] basic_view, exclude_t> view(exclude_t = {}) { + return {assure>(), assure>()..., assure()...}; + } + + /** + * @brief Returns a group for the given components. + * + * Groups are created on the fly and share with the registry its internal + * data structures. Feel free to discard them after the use.
+ * Creating and destroying a group is an incredibly cheap operation. As a + * rule of thumb, storing a group should never be an option. + * + * Groups support exclusion lists and can own types of components. The more + * types are owned by a group, the faster it is to iterate entities and + * components.
+ * However, groups also affect some features of the registry such as the + * creation and destruction of components. + * + * @note + * Pools of components that are owned by a group cannot be sorted anymore. + * The group takes the ownership of the pools and arrange components so as + * to iterate them as fast as possible. + * + * @tparam Owned Types of components owned by the group. + * @tparam Get Types of components observed by the group. + * @tparam Exclude Types of components used to filter the group. + * @return A newly created group. + */ + template + [[nodiscard]] basic_group, get_t, exclude_t> group(get_t, exclude_t = {}) { + static_assert(sizeof...(Owned) + sizeof...(Get) > 0, "Exclusion-only groups are not supported"); + static_assert(sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude) > 1, "Single component groups are not allowed"); + + using handler_type = group_handler...>, get_t...>, std::remove_const_t...>; + + const auto cpools = std::forward_as_tuple(assure>()..., assure>()...); + constexpr auto size = sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude); + handler_type *handler = nullptr; + + auto it = std::find_if(groups.cbegin(), groups.cend(), [size](const auto &gdata) { + return gdata.size == size + && (gdata.owned(type_hash>::value()) && ...) + && (gdata.get(type_hash>::value()) && ...) + && (gdata.exclude(type_hash::value()) && ...); + }); + + if(it != groups.cend()) { + handler = static_cast(it->group.get()); + } else { + group_data candidate = { + size, + {new handler_type{}, [](void *instance) { delete static_cast(instance); }}, + []([[maybe_unused]] const id_type ctype) ENTT_NOEXCEPT { return ((ctype == type_hash>::value()) || ...); }, + []([[maybe_unused]] const id_type ctype) ENTT_NOEXCEPT { return ((ctype == type_hash>::value()) || ...); }, + []([[maybe_unused]] const id_type ctype) ENTT_NOEXCEPT { return ((ctype == type_hash::value()) || ...); }, + }; + + handler = static_cast(candidate.group.get()); + + const void *maybe_valid_if = nullptr; + const void *discard_if = nullptr; + + if constexpr(sizeof...(Owned) == 0) { + groups.push_back(std::move(candidate)); + } else { + [[maybe_unused]] auto has_conflict = [size](const auto &gdata) { + const auto overlapping = (0u + ... + gdata.owned(type_hash>::value())); + const auto sz = overlapping + (0u + ... + gdata.get(type_hash>::value())) + (0u + ... + gdata.exclude(type_hash::value())); + return !overlapping || ((sz == size) || (sz == gdata.size)); + }; + + ENTT_ASSERT(std::all_of(groups.cbegin(), groups.cend(), std::move(has_conflict)), "Conflicting groups"); + + const auto next = std::find_if_not(groups.cbegin(), groups.cend(), [size](const auto &gdata) { + return !(0u + ... + gdata.owned(type_hash>::value())) || (size > gdata.size); + }); + + const auto prev = std::find_if(std::make_reverse_iterator(next), groups.crend(), [](const auto &gdata) { + return (0u + ... + gdata.owned(type_hash>::value())); + }); + + maybe_valid_if = (next == groups.cend() ? maybe_valid_if : next->group.get()); + discard_if = (prev == groups.crend() ? discard_if : prev->group.get()); + groups.insert(next, std::move(candidate)); + } + + (on_construct>().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if>>(*handler), ...); + (on_construct>().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if>>(*handler), ...); + (on_destroy().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if>(*handler), ...); + + (on_destroy>().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...); + (on_destroy>().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...); + (on_construct().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...); + + if constexpr(sizeof...(Owned) == 0) { + for(const auto entity: view(exclude)) { + handler->current.emplace(entity); + } + } else { + // we cannot iterate backwards because we want to leave behind valid entities in case of owned types + for(auto *first = std::get<0>(cpools).data(), *last = first + std::get<0>(cpools).size(); first != last; ++first) { + handler->template maybe_valid_if...>>>(*this, *first); + } + } + } + + return {handler->current, std::get> &>(cpools)..., std::get> &>(cpools)...}; + } + + /*! @copydoc group */ + template + [[nodiscard]] basic_group...>, get_t...>, exclude_t> group_if_exists(get_t, exclude_t = {}) const { + auto it = std::find_if(groups.cbegin(), groups.cend(), [](const auto &gdata) { + return gdata.size == (sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude)) + && (gdata.owned(type_hash>::value()) && ...) + && (gdata.get(type_hash>::value()) && ...) + && (gdata.exclude(type_hash::value()) && ...); + }); + + if(it == groups.cend()) { + return {}; + } else { + using handler_type = group_handler...>, get_t...>, std::remove_const_t...>; + return {static_cast(it->group.get())->current, assure>()..., assure>()...}; + } + } + + /*! @copydoc group */ + template + [[nodiscard]] basic_group, get_t<>, exclude_t> group(exclude_t = {}) { + return group(get_t<>{}, exclude); + } + + /*! @copydoc group */ + template + [[nodiscard]] basic_group...>, get_t<>, exclude_t> group_if_exists(exclude_t = {}) const { + return group_if_exists...>(get_t<>{}, exclude); + } + + /** + * @brief Checks whether the given components belong to any group. + * @tparam Component Types of components in which one is interested. + * @return True if the pools of the given components are _free_, false + * otherwise. + */ + template + [[nodiscard]] bool owned() const { + return std::any_of(groups.cbegin(), groups.cend(), [](auto &&gdata) { return (gdata.owned(type_hash>::value()) || ...); }); + } + + /** + * @brief Checks whether a group can be sorted. + * @tparam Owned Types of components owned by the group. + * @tparam Get Types of components observed by the group. + * @tparam Exclude Types of components used to filter the group. + * @return True if the group can be sorted, false otherwise. + */ + template + [[nodiscard]] bool sortable(const basic_group, get_t, exclude_t> &) ENTT_NOEXCEPT { + constexpr auto size = sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude); + auto pred = [size](const auto &gdata) { return (0u + ... + gdata.owned(type_hash>::value())) && (size < gdata.size); }; + return std::find_if(groups.cbegin(), groups.cend(), std::move(pred)) == groups.cend(); + } + + /** + * @brief Sorts the elements of a given component. + * + * The order remains valid until a component of the given type is assigned + * to or removed from an entity.
+ * 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 Component &, const Component &); + * @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 components owned by a group cannot be sorted. + * + * @tparam Component Type of components 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(std::is_invocable_v) { + auto comp = [&cpool, compare = std::move(compare)](const auto lhs, const auto rhs) { return compare(std::as_const(cpool.get(lhs)), std::as_const(cpool.get(rhs))); }; + cpool.sort(std::move(comp), std::move(algo), std::forward(args)...); + } else { + cpool.sort(std::move(compare), std::move(algo), std::forward(args)...); + } + } + + /** + * @brief Sorts two pools of components in the same way. + * + * Being `To` and `From` the two sets, after invoking this function an + * iterator for `To` returns elements according to the following rules: + * + * * All entities in `To` that are also in `From` are returned first + * according to the order they have in `From`. + * * All entities in `To` that are not in `From` are returned in no + * particular order after all the other entities. + * + * Any subsequent change to `From` won't affect the order in `To`. + * + * @warning + * Pools of components owned by a group cannot be sorted. + * + * @tparam To Type of components to sort. + * @tparam From Type of components to use to sort. + */ + template + void sort() { + ENTT_ASSERT(!owned(), "Cannot sort owned storage"); + assure().respect(assure()); + } + + /** + * @brief Returns the context object, that is, a general purpose container. + * @return The context object, that is, a general purpose container. + */ + context &ctx() ENTT_NOEXCEPT { + return vars; + } + + /*! @copydoc ctx */ + const context &ctx() const ENTT_NOEXCEPT { + return vars; + } + +private: + dense_map, identity> pools; + std::vector groups; + std::vector entities; + entity_type free_list; + context vars; +}; + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @brief Non-owning handle to an entity. + * + * Tiny wrapper around a registry and an entity. + * + * @tparam Entity A valid entity type (see entt_traits for more details). + * @tparam Type Types to which to restrict the scope of a handle. + */ +template +struct basic_handle { + /*! @brief Type of registry accepted by the handle. */ + using registry_type = constness_as_t>, Entity>; + /*! @brief Underlying entity identifier. */ + using entity_type = typename registry_type::entity_type; + /*! @brief Underlying version type. */ + using version_type = typename registry_type::version_type; + /*! @brief Unsigned integer type. */ + using size_type = typename registry_type::size_type; + + /*! @brief Constructs an invalid handle. */ + basic_handle() ENTT_NOEXCEPT + : reg{}, + 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) ENTT_NOEXCEPT + : reg{&ref}, + entt{value} {} + + /** + * @brief Constructs a const handle from a non-const one. + * @tparam Other A valid entity type (see entt_traits for more details). + * @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 ENTT_NOEXCEPT { + static_assert(std::is_same_v || std::is_same_v, Entity>, "Invalid conversion between different handles"); + static_assert((sizeof...(Type) == 0 || ((sizeof...(Args) != 0 && sizeof...(Args) <= sizeof...(Type)) && ... && (type_list_contains_v, Args>))), "Invalid conversion between different handles"); + + return reg ? basic_handle{*reg, entt} : basic_handle{}; + } + + /** + * @brief Converts a handle to its underlying entity. + * @return The contained identifier. + */ + [[nodiscard]] operator entity_type() const ENTT_NOEXCEPT { + return entity(); + } + + /** + * @brief Checks if a handle refers to non-null registry pointer and entity. + * @return True if the handle refers to non-null registry and entity, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return reg && reg->valid(entt); + } + + /** + * @brief Checks if a handle refers to a valid entity or not. + * @return True if the handle refers to a valid entity, false otherwise. + */ + [[nodiscard]] bool valid() const { + return reg->valid(entt); + } + + /** + * @brief Returns a pointer to the underlying registry, if any. + * @return A pointer to the underlying registry, if any. + */ + [[nodiscard]] registry_type *registry() const ENTT_NOEXCEPT { + return reg; + } + + /** + * @brief Returns the entity associated with a handle. + * @return The entity associated with the handle. + */ + [[nodiscard]] entity_type entity() const ENTT_NOEXCEPT { + return entt; + } + + /** + * @brief Destroys the entity associated with a handle. + * @sa basic_registry::destroy + */ + void destroy() { + reg->destroy(entt); + } + + /** + * @brief Destroys the entity associated with a handle. + * @sa basic_registry::destroy + * @param version A desired version upon destruction. + */ + void destroy(const version_type version) { + reg->destroy(entt, version); + } + + /** + * @brief Assigns the given component to a handle. + * @sa basic_registry::emplace + * @tparam Component Type of component to create. + * @tparam Args Types of arguments to use to construct the component. + * @param args Parameters to use to initialize the component. + * @return A reference to the newly created component. + */ + template + decltype(auto) emplace(Args &&...args) const { + static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v), "Invalid type"); + return reg->template emplace(entt, std::forward(args)...); + } + + /** + * @brief Assigns or replaces the given component for a handle. + * @sa basic_registry::emplace_or_replace + * @tparam Component Type of component to assign or replace. + * @tparam Args Types of arguments to use to construct the component. + * @param args Parameters to use to initialize the component. + * @return A reference to the newly created component. + */ + template + decltype(auto) emplace_or_replace(Args &&...args) const { + static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v), "Invalid type"); + return reg->template emplace_or_replace(entt, std::forward(args)...); + } + + /** + * @brief Patches the given component for a handle. + * @sa basic_registry::patch + * @tparam Component Type of component to patch. + * @tparam Func Types of the function objects to invoke. + * @param func Valid function objects. + * @return A reference to the patched component. + */ + template + decltype(auto) patch(Func &&...func) const { + static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v), "Invalid type"); + return reg->template patch(entt, std::forward(func)...); + } + + /** + * @brief Replaces the given component for a handle. + * @sa basic_registry::replace + * @tparam Component Type of component to replace. + * @tparam Args Types of arguments to use to construct the component. + * @param args Parameters to use to initialize the component. + * @return A reference to the component being replaced. + */ + template + decltype(auto) replace(Args &&...args) const { + static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v), "Invalid type"); + return reg->template replace(entt, std::forward(args)...); + } + + /** + * @brief Removes the given components from a handle. + * @sa basic_registry::remove + * @tparam Component Types of components to remove. + * @return The number of components actually removed. + */ + template + size_type remove() const { + static_assert(sizeof...(Type) == 0 || (type_list_contains_v, Component> && ...), "Invalid type"); + return reg->template remove(entt); + } + + /** + * @brief Erases the given components from a handle. + * @sa basic_registry::erase + * @tparam Component Types of components to erase. + */ + template + void erase() const { + static_assert(sizeof...(Type) == 0 || (type_list_contains_v, Component> && ...), "Invalid type"); + reg->template erase(entt); + } + + /** + * @brief Checks if a handle has all the given components. + * @sa basic_registry::all_of + * @tparam Component Components for which to perform the check. + * @return True if the handle has all the components, false otherwise. + */ + template + [[nodiscard]] decltype(auto) all_of() const { + return reg->template all_of(entt); + } + + /** + * @brief Checks if a handle has at least one of the given components. + * @sa basic_registry::any_of + * @tparam Component Components for which to perform the check. + * @return True if the handle has at least one of the given components, + * false otherwise. + */ + template + [[nodiscard]] decltype(auto) any_of() const { + return reg->template any_of(entt); + } + + /** + * @brief Returns references to the given components for a handle. + * @sa basic_registry::get + * @tparam Component Types of components to get. + * @return References to the components owned by the handle. + */ + template + [[nodiscard]] decltype(auto) get() const { + static_assert(sizeof...(Type) == 0 || (type_list_contains_v, Component> && ...), "Invalid type"); + return reg->template get(entt); + } + + /** + * @brief Returns a reference to the given component for a handle. + * @sa basic_registry::get_or_emplace + * @tparam Component Type of component to get. + * @tparam Args Types of arguments to use to construct the component. + * @param args Parameters to use to initialize the component. + * @return Reference to the component owned by the handle. + */ + template + [[nodiscard]] decltype(auto) get_or_emplace(Args &&...args) const { + static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v), "Invalid type"); + return reg->template get_or_emplace(entt, std::forward(args)...); + } + + /** + * @brief Returns pointers to the given components for a handle. + * @sa basic_registry::try_get + * @tparam Component Types of components to get. + * @return Pointers to the components owned by the handle. + */ + template + [[nodiscard]] auto try_get() const { + static_assert(sizeof...(Type) == 0 || (type_list_contains_v, Component> && ...), "Invalid type"); + return reg->template try_get(entt); + } + + /** + * @brief Checks if a handle has components assigned. + * @return True if the handle has no components assigned, false otherwise. + */ + [[nodiscard]] bool orphan() const { + return reg->orphan(entt); + } + + /** + * @brief Visits a handle and returns the pools for its components. + * + * The signature of the function should be equivalent to the following: + * + * @code{.cpp} + * void(id_type, const basic_sparse_set &); + * @endcode + * + * Returned pools are those that contain the entity associated with the + * handle. + * + * @tparam Func Type of the function object to invoke. + * @param func A valid function object. + */ + template + void visit(Func &&func) const { + for(auto [id, storage]: reg->storage()) { + if(storage.contains(entt)) { + func(id, storage); + } + } + } + +private: + registry_type *reg; + entity_type entt; +}; + +/** + * @brief Compares two handles. + * @tparam Args Scope of the first handle. + * @tparam Other Scope of the second handle. + * @param lhs A valid handle. + * @param rhs 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 &lhs, const basic_handle &rhs) ENTT_NOEXCEPT { + return lhs.registry() == rhs.registry() && lhs.entity() == rhs.entity(); +} + +/** + * @brief Compares two handles. + * @tparam Args Scope of the first handle. + * @tparam Other Scope of the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return False if both handles refer to the same registry and the same + * entity, true otherwise. + */ +template +[[nodiscard]] bool operator!=(const basic_handle &lhs, const basic_handle &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Deduction guide. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +basic_handle(basic_registry &, Entity) -> basic_handle; + +/** + * @brief Deduction guide. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +basic_handle(const basic_registry &, Entity) -> basic_handle; + +} // namespace entt + +#endif + +// #include "entity/helper.hpp" +#ifndef ENTT_ENTITY_HELPER_HPP +#define ENTT_ENTITY_HELPER_HPP + +#include +// #include "../config/config.h" + +// #include "../core/fwd.hpp" + +// #include "../core/type_traits.hpp" + +// #include "../signal/delegate.hpp" +#ifndef ENTT_SIGNAL_DELEGATE_HPP +#define ENTT_SIGNAL_DELEGATE_HPP + +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/type_traits.hpp" + +// #include "fwd.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +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 +auto function_pointer(Type Class::*, Other &&...) -> Type (*)(); + +template +using function_pointer_t = decltype(internal::function_pointer(std::declval()...)); + +template +[[nodiscard]] constexpr auto index_sequence_for(Ret (*)(Args...)) { + return std::index_sequence_for{}; +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @brief Used to wrap a function or a member of a specified type. */ +template +struct connect_arg_t {}; + +/*! @brief Constant of type connect_arg_t used to disambiguate calls. */ +template +inline constexpr connect_arg_t connect_arg{}; + +/** + * @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 { + template + [[nodiscard]] auto wrap(std::index_sequence) ENTT_NOEXCEPT { + return [](const void *, Args... args) -> Ret { + [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); + return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); + }; + } + + template + [[nodiscard]] auto wrap(Type &, std::index_sequence) ENTT_NOEXCEPT { + return [](const void *payload, Args... args) -> Ret { + [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); + Type *curr = static_cast(const_cast *>(payload)); + return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); + }; + } + + template + [[nodiscard]] auto wrap(Type *, std::index_sequence) ENTT_NOEXCEPT { + return [](const void *payload, Args... args) -> Ret { + [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); + Type *curr = static_cast(const_cast *>(payload)); + return static_cast(std::invoke(Candidate, curr, std::forward>>(std::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() ENTT_NOEXCEPT + : instance{nullptr}, + fn{nullptr} {} + + /** + * @brief Constructs a delegate and connects a free function or an unbound + * member. + * @tparam Candidate Function or member to connect to the delegate. + */ + template + delegate(connect_arg_t) ENTT_NOEXCEPT { + connect(); + } + + /** + * @brief Constructs a delegate and connects a free function with payload or + * a bound member. + * @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 object that fits the purpose. + */ + template + delegate(connect_arg_t, Type &&value_or_instance) ENTT_NOEXCEPT { + connect(std::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) ENTT_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() ENTT_NOEXCEPT { + instance = nullptr; + + if constexpr(std::is_invocable_r_v) { + fn = [](const void *, Args... args) -> Ret { + return Ret(std::invoke(Candidate, std::forward(args)...)); + }; + } else if constexpr(std::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) ENTT_NOEXCEPT { + instance = &value_or_instance; + + if constexpr(std::is_invocable_r_v) { + fn = [](const void *payload, Args... args) -> Ret { + Type *curr = static_cast(const_cast *>(payload)); + return Ret(std::invoke(Candidate, *curr, std::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) ENTT_NOEXCEPT { + instance = value_or_instance; + + if constexpr(std::is_invocable_r_v) { + fn = [](const void *payload, Args... args) -> Ret { + Type *curr = static_cast(const_cast *>(payload)); + return Ret(std::invoke(Candidate, curr, std::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) ENTT_NOEXCEPT { + instance = payload; + fn = function; + } + + /** + * @brief Resets a delegate. + * + * After a reset, a delegate cannot be invoked anymore. + */ + void reset() ENTT_NOEXCEPT { + instance = nullptr; + fn = nullptr; + } + + /** + * @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 ENTT_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, std::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 ENTT_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 ENTT_NOEXCEPT { + return fn == other.fn && instance == other.instance; + } + +private: + const void *instance; + function_type *fn; +}; + +/** + * @brief Compares the contents of two delegates. + * @tparam Ret Return type of a function type. + * @tparam Args Types of arguments of a function type. + * @param lhs A valid delegate object. + * @param rhs A valid delegate object. + * @return True if the two contents differ, false otherwise. + */ +template +[[nodiscard]] bool operator!=(const delegate &lhs, const delegate &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @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 + +// #include "fwd.hpp" + +// #include "registry.hpp" + + +namespace entt { + +/** + * @brief Converts a registry to a view. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +struct as_view { + /*! @brief Underlying entity identifier. */ + using entity_type = std::remove_const_t; + /*! @brief Type of registry to convert. */ + using registry_type = constness_as_t, Entity>; + + /** + * @brief Constructs a converter for a given registry. + * @param source A valid reference to a registry. + */ + as_view(registry_type &source) ENTT_NOEXCEPT: reg{source} {} + + /** + * @brief Conversion function from a registry to a view. + * @tparam Exclude Types of components used to filter the view. + * @tparam Component Type of components used to construct the view. + * @return A newly created view. + */ + template + operator basic_view, Exclude>() const { + return reg.template view(Exclude{}); + } + +private: + registry_type ® +}; + +/** + * @brief Deduction guide. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +as_view(basic_registry &) -> as_view; + +/** + * @brief Deduction guide. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +as_view(const basic_registry &) -> as_view; + +/** + * @brief Converts a registry to a group. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +struct as_group { + /*! @brief Underlying entity identifier. */ + using entity_type = std::remove_const_t; + /*! @brief Type of registry to convert. */ + using registry_type = constness_as_t, Entity>; + + /** + * @brief Constructs a converter for a given registry. + * @param source A valid reference to a registry. + */ + as_group(registry_type &source) ENTT_NOEXCEPT: reg{source} {} + + /** + * @brief Conversion function from a registry to a group. + * @tparam Get Types of components observed by the group. + * @tparam Exclude Types of components used to filter the group. + * @tparam Owned Types of components owned by the group. + * @return A newly created group. + */ + template + operator basic_group, Get, Exclude>() const { + if constexpr(std::is_const_v) { + return reg.template group_if_exists(Get{}, Exclude{}); + } else { + return reg.template group(Get{}, Exclude{}); + } + } + +private: + registry_type ® +}; + +/** + * @brief Deduction guide. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +as_group(basic_registry &) -> as_group; + +/** + * @brief Deduction guide. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +as_group(const basic_registry &) -> as_group; + +/** + * @brief Helper to create a listener that directly invokes a member function. + * @tparam Member Member function to invoke on a component of the given type. + * @tparam Entity A valid entity type (see entt_traits for more details). + * @param reg A registry that contains the given entity and its components. + * @param entt Entity from which to get the component. + */ +template +void invoke(basic_registry ®, const Entity entt) { + static_assert(std::is_member_function_pointer_v, "Invalid pointer to non-static member function"); + delegate &, const Entity)> func; + func.template connect(reg.template get>(entt)); + func(reg, entt); +} + +/** + * @brief Returns the entity associated with a given component. + * + * @warning + * Currently, this function only works correctly with the default pool as it + * makes assumptions about how the components are laid out. + * + * @tparam Entity A valid entity type (see entt_traits for more details). + * @tparam Component Type of component. + * @param reg A registry that contains the given entity and its components. + * @param instance A valid component instance. + * @return The entity associated with the given component. + */ +template +Entity to_entity(const basic_registry ®, const Component &instance) { + const auto &storage = reg.template storage(); + const typename basic_registry::base_type &base = storage; + const auto *addr = std::addressof(instance); + + for(auto it = base.rbegin(), last = base.rend(); it < last; it += ENTT_PACKED_PAGE) { + if(const auto dist = (addr - std::addressof(storage.get(*it))); dist >= 0 && dist < ENTT_PACKED_PAGE) { + return *(it + dist); + } + } + + return null; +} + +} // namespace entt + +#endif + +// #include "entity/observer.hpp" +#ifndef ENTT_ENTITY_OBSERVER_HPP +#define ENTT_ENTITY_OBSERVER_HPP + +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/type_traits.hpp" + +// #include "../signal/delegate.hpp" + +// #include "entity.hpp" + +// #include "fwd.hpp" + +// #include "registry.hpp" + +// #include "storage.hpp" + +// #include "utility.hpp" + + +namespace entt { + +/*! @brief Grouping matcher. */ +template +struct matcher {}; + +/** + * @brief Collector. + * + * Primary template isn't defined on purpose. All the specializations give a + * compile-time error, but for a few reasonable cases. + */ +template +struct basic_collector; + +/** + * @brief Collector. + * + * A collector contains a set of rules (literally, matchers) to use to track + * entities.
+ * Its main purpose is to generate a descriptor that allows an observer to know + * how to connect to a registry. + */ +template<> +struct basic_collector<> { + /** + * @brief Adds a grouping matcher to the collector. + * @tparam AllOf Types of components tracked by the matcher. + * @tparam NoneOf Types of components used to filter out entities. + * @return The updated collector. + */ + template + static constexpr auto group(exclude_t = {}) ENTT_NOEXCEPT { + return basic_collector, type_list<>, type_list, AllOf...>>{}; + } + + /** + * @brief Adds an observing matcher to the collector. + * @tparam AnyOf Type of component for which changes should be detected. + * @return The updated collector. + */ + template + static constexpr auto update() ENTT_NOEXCEPT { + return basic_collector, type_list<>, AnyOf>>{}; + } +}; + +/** + * @brief Collector. + * @copydetails basic_collector<> + * @tparam Reject Untracked types used to filter out entities. + * @tparam Require Untracked types required by the matcher. + * @tparam Rule Specific details of the current matcher. + * @tparam Other Other matchers. + */ +template +struct basic_collector, type_list, Rule...>, Other...> { + /*! @brief Current matcher. */ + using current_type = matcher, type_list, Rule...>; + + /** + * @brief Adds a grouping matcher to the collector. + * @tparam AllOf Types of components tracked by the matcher. + * @tparam NoneOf Types of components used to filter out entities. + * @return The updated collector. + */ + template + static constexpr auto group(exclude_t = {}) ENTT_NOEXCEPT { + return basic_collector, type_list<>, type_list, AllOf...>, current_type, Other...>{}; + } + + /** + * @brief Adds an observing matcher to the collector. + * @tparam AnyOf Type of component for which changes should be detected. + * @return The updated collector. + */ + template + static constexpr auto update() ENTT_NOEXCEPT { + return basic_collector, type_list<>, AnyOf>, current_type, Other...>{}; + } + + /** + * @brief Updates the filter of the last added matcher. + * @tparam AllOf Types of components required by the matcher. + * @tparam NoneOf Types of components used to filter out entities. + * @return The updated collector. + */ + template + static constexpr auto where(exclude_t = {}) ENTT_NOEXCEPT { + using extended_type = matcher, type_list, Rule...>; + return basic_collector{}; + } +}; + +/*! @brief Variable template used to ease the definition of collectors. */ +inline constexpr basic_collector<> collector{}; + +/** + * @brief Observer. + * + * An observer returns all the entities and only the entities that fit the + * requirements of at least one matcher. Moreover, it's guaranteed that the + * entity list is tightly packed in memory for fast iterations.
+ * In general, observers don't stay true to the order of any set of components. + * + * Observers work mainly with two types of matchers, provided through a + * collector: + * + * * Observing matcher: an observer will return at least all the living entities + * for which one or more of the given components have been updated and not yet + * destroyed. + * * Grouping matcher: an observer will return at least all the living entities + * that would have entered the given group if it existed and that would have + * not yet left it. + * + * If an entity respects the requirements of multiple matchers, it will be + * returned once and only once by the observer in any case. + * + * Matchers support also filtering by means of a _where_ clause that accepts + * both a list of types and an exclusion list.
+ * Whenever a matcher finds that an entity matches its requirements, the + * condition of the filter is verified before to register the entity itself. + * Moreover, a registered entity isn't returned by the observer if the condition + * set by the filter is broken in the meantime. + * + * @b Important + * + * Iterators aren't invalidated if: + * + * * New instances of the given components are created and assigned to entities. + * * The entity currently pointed is modified (as an example, if one of the + * given components is removed from the entity to which the iterator points). + * * The entity currently pointed is destroyed. + * + * In all the other cases, modifying the pools of the given components in any + * way invalidates all the iterators and using them results in undefined + * behavior. + * + * @warning + * Lifetime of an observer doesn't necessarily have to overcome that of the + * registry to which it is connected. However, the observer must be disconnected + * from the registry before being destroyed to avoid crashes due to dangling + * pointers. + * + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +class basic_observer { + using payload_type = std::uint32_t; + + template + struct matcher_handler; + + template + struct matcher_handler, type_list, AnyOf>> { + template + static void maybe_valid_if(basic_observer &obs, basic_registry ®, const Entity entt) { + if(reg.template all_of(entt) && !reg.template any_of(entt)) { + if(!obs.storage.contains(entt)) { + obs.storage.emplace(entt); + } + + obs.storage.get(entt) |= (1 << Index); + } + } + + template + static void discard_if(basic_observer &obs, basic_registry &, const Entity entt) { + if(obs.storage.contains(entt) && !(obs.storage.get(entt) &= (~(1 << Index)))) { + obs.storage.erase(entt); + } + } + + template + static void connect(basic_observer &obs, basic_registry ®) { + (reg.template on_destroy().template connect<&discard_if>(obs), ...); + (reg.template on_construct().template connect<&discard_if>(obs), ...); + reg.template on_update().template connect<&maybe_valid_if>(obs); + reg.template on_destroy().template connect<&discard_if>(obs); + } + + static void disconnect(basic_observer &obs, basic_registry ®) { + (reg.template on_destroy().disconnect(obs), ...); + (reg.template on_construct().disconnect(obs), ...); + reg.template on_update().disconnect(obs); + reg.template on_destroy().disconnect(obs); + } + }; + + template + struct matcher_handler, type_list, type_list, AllOf...>> { + template + static void maybe_valid_if(basic_observer &obs, basic_registry ®, const Entity entt) { + auto condition = [®, entt]() { + if constexpr(sizeof...(Ignore) == 0) { + return reg.template all_of(entt) && !reg.template any_of(entt); + } else { + return reg.template all_of(entt) && ((std::is_same_v || !reg.template any_of(entt)) && ...) && !reg.template any_of(entt); + } + }; + + if(condition()) { + if(!obs.storage.contains(entt)) { + obs.storage.emplace(entt); + } + + obs.storage.get(entt) |= (1 << Index); + } + } + + template + static void discard_if(basic_observer &obs, basic_registry &, const Entity entt) { + if(obs.storage.contains(entt) && !(obs.storage.get(entt) &= (~(1 << Index)))) { + obs.storage.erase(entt); + } + } + + template + static void connect(basic_observer &obs, basic_registry ®) { + (reg.template on_destroy().template connect<&discard_if>(obs), ...); + (reg.template on_construct().template connect<&discard_if>(obs), ...); + (reg.template on_construct().template connect<&maybe_valid_if>(obs), ...); + (reg.template on_destroy().template connect<&maybe_valid_if>(obs), ...); + (reg.template on_destroy().template connect<&discard_if>(obs), ...); + (reg.template on_construct().template connect<&discard_if>(obs), ...); + } + + static void disconnect(basic_observer &obs, basic_registry ®) { + (reg.template on_destroy().disconnect(obs), ...); + (reg.template on_construct().disconnect(obs), ...); + (reg.template on_construct().disconnect(obs), ...); + (reg.template on_destroy().disconnect(obs), ...); + (reg.template on_destroy().disconnect(obs), ...); + (reg.template on_construct().disconnect(obs), ...); + } + }; + + template + static void disconnect(basic_registry ®, basic_observer &obs) { + (matcher_handler::disconnect(obs, reg), ...); + } + + template + void connect(basic_registry ®, std::index_sequence) { + static_assert(sizeof...(Matcher) < std::numeric_limits::digits, "Too many matchers"); + (matcher_handler::template connect(*this, reg), ...); + release.template connect<&basic_observer::disconnect>(reg); + } + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Random access iterator type. */ + using iterator = typename basic_sparse_set::iterator; + + /*! @brief Default constructor. */ + basic_observer() + : release{}, + storage{} {} + + /*! @brief Default copy constructor, deleted on purpose. */ + basic_observer(const basic_observer &) = delete; + /*! @brief Default move constructor, deleted on purpose. */ + basic_observer(basic_observer &&) = delete; + + /** + * @brief Creates an observer and connects it to a given registry. + * @tparam Matcher Types of matchers to use to initialize the observer. + * @param reg A valid reference to a registry. + */ + template + basic_observer(basic_registry ®, basic_collector) + : basic_observer{} { + connect(reg, std::index_sequence_for{}); + } + + /*! @brief Default destructor. */ + ~basic_observer() = default; + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This observer. + */ + basic_observer &operator=(const basic_observer &) = delete; + + /** + * @brief Default move assignment operator, deleted on purpose. + * @return This observer. + */ + basic_observer &operator=(basic_observer &&) = delete; + + /** + * @brief Connects an observer to a given registry. + * @tparam Matcher Types of matchers to use to initialize the observer. + * @param reg A valid reference to a registry. + */ + template + void connect(basic_registry ®, basic_collector) { + disconnect(); + connect(reg, std::index_sequence_for{}); + storage.clear(); + } + + /*! @brief Disconnects an observer from the registry it keeps track of. */ + void disconnect() { + if(release) { + release(*this); + release.reset(); + } + } + + /** + * @brief Returns the number of elements in an observer. + * @return Number of elements. + */ + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return storage.size(); + } + + /** + * @brief Checks whether an observer is empty. + * @return True if the observer is empty, false otherwise. + */ + [[nodiscard]] bool empty() const ENTT_NOEXCEPT { + return storage.empty(); + } + + /** + * @brief Direct access to the list of entities of the observer. + * + * The returned pointer is such that range `[data(), data() + size())` is + * always a valid range, even if the container is empty. + * + * @note + * Entities are in the reverse order as returned by the `begin`/`end` + * iterators. + * + * @return A pointer to the array of entities. + */ + [[nodiscard]] const entity_type *data() const ENTT_NOEXCEPT { + return storage.data(); + } + + /** + * @brief Returns an iterator to the first entity of the observer. + * + * The returned iterator points to the first entity of the observer. If the + * container is empty, the returned iterator will be equal to `end()`. + * + * @return An iterator to the first entity of the observer. + */ + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return storage.basic_sparse_set::begin(); + } + + /** + * @brief Returns an iterator that is past the last entity of the observer. + * + * The returned iterator points to the entity following the last entity of + * the observer. Attempting to dereference the returned iterator results in + * undefined behavior. + * + * @return An iterator to the entity following the last entity of the + * observer. + */ + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return storage.basic_sparse_set::end(); + } + + /*! @brief Clears the underlying container. */ + void clear() ENTT_NOEXCEPT { + storage.clear(); + } + + /** + * @brief Iterates entities and applies the given function object to them. + * + * The function object is invoked for each entity.
+ * The signature of the function must be equivalent to the following form: + * + * @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); + } + } + + /** + * @brief Iterates entities and applies the given function object to them, + * then clears the observer. + * + * @sa each + * + * @tparam Func Type of the function object to invoke. + * @param func A valid function object. + */ + template + void each(Func func) { + std::as_const(*this).each(std::move(func)); + clear(); + } + +private: + delegate release; + basic_storage storage; +}; + +} // namespace entt + +#endif + +// #include "entity/organizer.hpp" +#ifndef ENTT_ENTITY_ORGANIZER_HPP +#define ENTT_ENTITY_ORGANIZER_HPP + +#include +#include +#include +#include +#include +// #include "../container/dense_map.hpp" + +// #include "../core/type_info.hpp" + +// #include "../core/type_traits.hpp" + +// #include "../core/utility.hpp" + +// #include "fwd.hpp" + +// #include "helper.hpp" +#ifndef ENTT_ENTITY_HELPER_HPP +#define ENTT_ENTITY_HELPER_HPP + +#include +// #include "../config/config.h" + +// #include "../core/fwd.hpp" + +// #include "../core/type_traits.hpp" + +// #include "../signal/delegate.hpp" + +// #include "fwd.hpp" + +// #include "registry.hpp" + + +namespace entt { + +/** + * @brief Converts a registry to a view. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +struct as_view { + /*! @brief Underlying entity identifier. */ + using entity_type = std::remove_const_t; + /*! @brief Type of registry to convert. */ + using registry_type = constness_as_t, Entity>; + + /** + * @brief Constructs a converter for a given registry. + * @param source A valid reference to a registry. + */ + as_view(registry_type &source) ENTT_NOEXCEPT: reg{source} {} + + /** + * @brief Conversion function from a registry to a view. + * @tparam Exclude Types of components used to filter the view. + * @tparam Component Type of components used to construct the view. + * @return A newly created view. + */ + template + operator basic_view, Exclude>() const { + return reg.template view(Exclude{}); + } + +private: + registry_type ® +}; + +/** + * @brief Deduction guide. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +as_view(basic_registry &) -> as_view; + +/** + * @brief Deduction guide. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +as_view(const basic_registry &) -> as_view; + +/** + * @brief Converts a registry to a group. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +struct as_group { + /*! @brief Underlying entity identifier. */ + using entity_type = std::remove_const_t; + /*! @brief Type of registry to convert. */ + using registry_type = constness_as_t, Entity>; + + /** + * @brief Constructs a converter for a given registry. + * @param source A valid reference to a registry. + */ + as_group(registry_type &source) ENTT_NOEXCEPT: reg{source} {} + + /** + * @brief Conversion function from a registry to a group. + * @tparam Get Types of components observed by the group. + * @tparam Exclude Types of components used to filter the group. + * @tparam Owned Types of components owned by the group. + * @return A newly created group. + */ + template + operator basic_group, Get, Exclude>() const { + if constexpr(std::is_const_v) { + return reg.template group_if_exists(Get{}, Exclude{}); + } else { + return reg.template group(Get{}, Exclude{}); + } + } + +private: + registry_type ® +}; + +/** + * @brief Deduction guide. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +as_group(basic_registry &) -> as_group; + +/** + * @brief Deduction guide. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +as_group(const basic_registry &) -> as_group; + +/** + * @brief Helper to create a listener that directly invokes a member function. + * @tparam Member Member function to invoke on a component of the given type. + * @tparam Entity A valid entity type (see entt_traits for more details). + * @param reg A registry that contains the given entity and its components. + * @param entt Entity from which to get the component. + */ +template +void invoke(basic_registry ®, const Entity entt) { + static_assert(std::is_member_function_pointer_v, "Invalid pointer to non-static member function"); + delegate &, const Entity)> func; + func.template connect(reg.template get>(entt)); + func(reg, entt); +} + +/** + * @brief Returns the entity associated with a given component. + * + * @warning + * Currently, this function only works correctly with the default pool as it + * makes assumptions about how the components are laid out. + * + * @tparam Entity A valid entity type (see entt_traits for more details). + * @tparam Component Type of component. + * @param reg A registry that contains the given entity and its components. + * @param instance A valid component instance. + * @return The entity associated with the given component. + */ +template +Entity to_entity(const basic_registry ®, const Component &instance) { + const auto &storage = reg.template storage(); + const typename basic_registry::base_type &base = storage; + const auto *addr = std::addressof(instance); + + for(auto it = base.rbegin(), last = base.rend(); it < last; it += ENTT_PACKED_PAGE) { + if(const auto dist = (addr - std::addressof(storage.get(*it))); dist >= 0 && dist < ENTT_PACKED_PAGE) { + return *(it + dist); + } + } + + return null; +} + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct is_view: std::false_type {}; + +template +struct is_view, exclude_t>>: std::true_type {}; + +template +inline constexpr bool is_view_v = is_view::value; + +template +struct unpack_type { + using ro = std::conditional_t< + type_list_contains_v> || (std::is_const_v && !type_list_contains_v>), + type_list>, + type_list<>>; + + using rw = std::conditional_t< + type_list_contains_v> || (!std::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>::ro...>; + using rw = type_list_cat_t>::rw...>; +}; + +template +struct unpack_type, exclude_t>, type_list> + : unpack_type, 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...>; +}; + +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 + +/** + * Internal details not to be documented. + * @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 Entity A valid entity type (see entt_traits for more details). + */ +template +class basic_organizer final { + using callback_type = void(const void *, basic_registry &); + using prepare_type = void(basic_registry &); + using dependency_type = std::size_t(const bool, const type_info **, const std::size_t); + + struct vertex_data final { + std::size_t ro_count{}; + std::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(basic_registry ®) { + if constexpr(std::is_same_v>) { + return reg; + } else if constexpr(internal::is_view_v) { + return as_view{reg}; + } else { + return reg.ctx().template emplace>(); + } + } + + template + [[nodiscard]] static auto to_args(basic_registry ®, type_list) { + return std::tuple(reg))...>(extract(reg)...); + } + + template + static std::size_t fill_dependencies(type_list, [[maybe_unused]] const type_info **buffer, [[maybe_unused]] const std::size_t count) { + if constexpr(sizeof...(Type) == 0u) { + return {}; + } else { + const type_info *info[sizeof...(Type)]{&type_id()...}; + const auto length = (std::min)(count, sizeof...(Type)); + std::copy_n(info, length, buffer); + return length; + } + } + + template + void track_dependencies(std::size_t index, const bool requires_registry, type_list, type_list) { + dependencies[type_hash>::value()].emplace_back(index, requires_registry || (sizeof...(RO) + sizeof...(RW) == 0u)); + (dependencies[type_hash::value()].emplace_back(index, false), ...); + (dependencies[type_hash::value()].emplace_back(index, true), ...); + } + + [[nodiscard]] std::vector adjacency_matrix() { + const auto length = vertices.size(); + std::vector edges(length * length, false); + + // creates the adjacency matrix + for(const auto &deps: dependencies) { + const auto last = deps.second.cend(); + auto it = deps.second.cbegin(); + + while(it != last) { + if(it->second) { + // rw item + if(auto curr = it++; it != last) { + if(it->second) { + edges[curr->first * length + it->first] = true; + } else { + if(const auto next = std::find_if(it, last, [](const auto &elem) { return elem.second; }); next != last) { + for(; it != next; ++it) { + edges[curr->first * length + it->first] = true; + edges[it->first * length + next->first] = true; + } + } else { + for(; it != next; ++it) { + edges[curr->first * length + it->first] = true; + } + } + } + } + } else { + // ro item, possibly only on first iteration + if(const auto next = std::find_if(it, last, [](const auto &elem) { return elem.second; }); next != last) { + for(; it != next; ++it) { + edges[it->first * length + next->first] = true; + } + } else { + it = last; + } + } + } + } + + // computes the transitive closure + for(std::size_t vk{}; vk < length; ++vk) { + for(std::size_t vi{}; vi < length; ++vi) { + for(std::size_t vj{}; vj < length; ++vj) { + edges[vi * length + vj] = edges[vi * length + vj] || (edges[vi * length + vk] && edges[vk * length + vj]); + } + } + } + + // applies the transitive reduction + for(std::size_t vert{}; vert < length; ++vert) { + edges[vert * length + vert] = false; + } + + for(std::size_t vj{}; vj < length; ++vj) { + for(std::size_t vi{}; vi < length; ++vi) { + if(edges[vi * length + vj]) { + for(std::size_t vk{}; vk < length; ++vk) { + if(edges[vj * length + vk]) { + edges[vi * length + vk] = false; + } + } + } + } + } + + return edges; + } + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::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 vtype True if the vertex is a top-level one, false otherwise. + * @param data The data associated with the vertex. + * @param edges The indices of the children in the adjacency list. + */ + vertex(const bool vtype, vertex_data data, std::vector edges) + : is_top_level{vtype}, + node{std::move(data)}, + reachable{std::move(edges)} {} + + /** + * @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. + */ + size_type ro_dependency(const type_info **buffer, const std::size_t length) const ENTT_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. + */ + size_type rw_dependency(const type_info **buffer, const std::size_t length) const ENTT_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. + */ + size_type ro_count() const ENTT_NOEXCEPT { + return node.ro_count; + } + + /** + * @brief Returns the number of writable resources of a vertex. + * @return The number of writable resources of the vertex. + */ + size_type rw_count() const ENTT_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. + */ + bool top_level() const ENTT_NOEXCEPT { + return is_top_level; + } + + /** + * @brief Returns a type info object associated with a vertex. + * @return A properly initialized type info object. + */ + const type_info &info() const ENTT_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. + */ + const char *name() const ENTT_NOEXCEPT { + return node.name; + } + + /** + * @brief Returns the function associated with a vertex. + * @return The function associated with the vertex. + */ + function_type *callback() const ENTT_NOEXCEPT { + return node.callback; + } + + /** + * @brief Returns the payload associated with a vertex, if any. + * @return The payload associated with the vertex, if any. + */ + const void *data() const ENTT_NOEXCEPT { + return node.payload; + } + + /** + * @brief Returns the list of nodes reachable from a given vertex. + * @return The list of nodes reachable from the vertex. + */ + const std::vector &children() const ENTT_NOEXCEPT { + return reachable; + } + + /** + * @brief Prepares a registry and assures that all required resources + * are properly instantiated before using them. + * @param reg A valid registry. + */ + void prepare(basic_registry ®) const { + node.prepare ? node.prepare(reg) : void(); + } + + private: + bool is_top_level; + vertex_data node; + std::vector reachable; + }; + + /** + * @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)); + constexpr auto requires_registry = type_list_contains_v>; + + callback_type *callback = +[](const void *, basic_registry ®) { + std::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 std::size_t length) { return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length); }, + +[](basic_registry ®) { void(to_args(reg, typename resource_type::args{})); }, + &type_id>()}; + + track_dependencies(vertices.size(), requires_registry, typename resource_type::ro{}, typename resource_type::rw{}); + vertices.push_back(std::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)); + constexpr auto requires_registry = type_list_contains_v>; + + callback_type *callback = +[](const void *payload, basic_registry ®) { + Type *curr = static_cast(const_cast *>(payload)); + std::apply(Candidate, std::tuple_cat(std::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 std::size_t length) { return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length); }, + +[](basic_registry ®) { void(to_args(reg, typename resource_type::args{})); }, + &type_id>()}; + + track_dependencies(vertices.size(), requires_registry, typename resource_type::ro{}, typename resource_type::rw{}); + vertices.push_back(std::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 std::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(std::move(vdata)); + } + + /** + * @brief Generates a task graph for the current content. + * @return The adjacency list of the task graph. + */ + std::vector graph() { + const auto edges = adjacency_matrix(); + + // creates the adjacency list + std::vector adjacency_list{}; + adjacency_list.reserve(vertices.size()); + + for(std::size_t col{}, length = vertices.size(); col < length; ++col) { + std::vector reachable{}; + const auto row = col * length; + bool is_top_level = true; + + for(std::size_t next{}; next < length; ++next) { + if(edges[row + next]) { + reachable.push_back(next); + } + } + + for(std::size_t next{}; next < length && is_top_level; ++next) { + is_top_level = !edges[next * length + col]; + } + + adjacency_list.emplace_back(is_top_level, vertices[col], std::move(reachable)); + } + + return adjacency_list; + } + + /*! @brief Erases all elements from a container. */ + void clear() { + dependencies.clear(); + vertices.clear(); + } + +private: + dense_map>, identity> dependencies; + std::vector vertices; +}; + +} // namespace entt + +#endif + +// #include "entity/registry.hpp" +#ifndef ENTT_ENTITY_REGISTRY_HPP +#define ENTT_ENTITY_REGISTRY_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../container/dense_map.hpp" + +// #include "../core/algorithm.hpp" + +// #include "../core/any.hpp" + +// #include "../core/fwd.hpp" + +// #include "../core/iterator.hpp" + +// #include "../core/type_info.hpp" + +// #include "../core/type_traits.hpp" + +// #include "../core/utility.hpp" + +// #include "component.hpp" + +// #include "entity.hpp" + +// #include "fwd.hpp" + +// #include "group.hpp" + +// #include "runtime_view.hpp" + +// #include "sparse_set.hpp" + +// #include "storage.hpp" + +// #include "utility.hpp" + +// #include "view.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +class storage_proxy_iterator final { + template + friend class storage_proxy_iterator; + + using mapped_type = std::remove_reference_t()->second)>; + +public: + using value_type = std::pair &>; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + storage_proxy_iterator() ENTT_NOEXCEPT + : it{} {} + + storage_proxy_iterator(const It iter) ENTT_NOEXCEPT + : it{iter} {} + + template && std::is_constructible_v>> + storage_proxy_iterator(const storage_proxy_iterator &other) ENTT_NOEXCEPT + : it{other.it} {} + + storage_proxy_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } + + storage_proxy_iterator operator++(int) ENTT_NOEXCEPT { + storage_proxy_iterator orig = *this; + return ++(*this), orig; + } + + storage_proxy_iterator &operator--() ENTT_NOEXCEPT { + return --it, *this; + } + + storage_proxy_iterator operator--(int) ENTT_NOEXCEPT { + storage_proxy_iterator orig = *this; + return operator--(), orig; + } + + storage_proxy_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + it += value; + return *this; + } + + storage_proxy_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + storage_proxy_iterator copy = *this; + return (copy += value); + } + + storage_proxy_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); + } + + storage_proxy_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); + } + + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + return {it[value].first, *it[value].second}; + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return {it->first, *it->second}; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + template + friend std::ptrdiff_t operator-(const storage_proxy_iterator &, const storage_proxy_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator==(const storage_proxy_iterator &, const storage_proxy_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator<(const storage_proxy_iterator &, const storage_proxy_iterator &) ENTT_NOEXCEPT; + +private: + It it; +}; + +template +[[nodiscard]] std::ptrdiff_t operator-(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it - rhs.it; +} + +template +[[nodiscard]] bool operator==(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] bool operator!=(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +[[nodiscard]] bool operator<(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it < rhs.it; +} + +template +[[nodiscard]] bool operator>(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +template +[[nodiscard]] bool operator<=(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +template +[[nodiscard]] bool operator>=(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +struct registry_context { + template + Type &emplace_hint(const id_type id, Args &&...args) { + return any_cast(data.try_emplace(id, std::in_place_type, std::forward(args)...).first->second); + } + + template + Type &emplace(Args &&...args) { + return emplace_hint(type_id().hash(), std::forward(args)...); + } + + template + bool erase(const id_type id = type_id().hash()) { + const auto it = data.find(id); + return it != data.end() && it->second.type() == type_id() ? (data.erase(it), true) : false; + } + + template + [[nodiscard]] std::add_const_t &at(const id_type id = type_id().hash()) const { + return any_cast &>(data.at(id)); + } + + template + [[nodiscard]] Type &at(const id_type id = type_id().hash()) { + return any_cast(data.at(id)); + } + + template + [[nodiscard]] std::add_const_t *find(const id_type id = type_id().hash()) const { + const auto it = data.find(id); + return it != data.cend() ? any_cast>(&it->second) : nullptr; + } + + template + [[nodiscard]] Type *find(const id_type id = type_id().hash()) { + const auto it = data.find(id); + return it != data.end() ? any_cast(&it->second) : nullptr; + } + + template + [[nodiscard]] bool contains(const id_type id = type_id().hash()) const { + const auto it = data.find(id); + return it != data.end() && it->second.type() == type_id(); + } + +private: + dense_map, identity> data; +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Fast and reliable entity-component system. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +class basic_registry { + using entity_traits = entt_traits; + using basic_common_type = basic_sparse_set; + + template + using storage_type = typename storage_traits::storage_type; + + template + struct group_handler; + + template + struct group_handler, get_t, Owned...> { + // nasty workaround for an issue with the toolset v141 that doesn't accept a fold expression here + static_assert(!std::disjunction_v::in_place_delete>...>, "Groups do not support in-place delete"); + std::conditional_t current{}; + + template + void maybe_valid_if(basic_registry &owner, const Entity entt) { + [[maybe_unused]] const auto cpools = std::forward_as_tuple(owner.assure()...); + + const auto is_valid = ((std::is_same_v || std::get &>(cpools).contains(entt)) && ...) + && ((std::is_same_v || owner.assure().contains(entt)) && ...) + && ((std::is_same_v || !owner.assure().contains(entt)) && ...); + + if constexpr(sizeof...(Owned) == 0) { + if(is_valid && !current.contains(entt)) { + current.emplace(entt); + } + } else { + if(is_valid && !(std::get<0>(cpools).index(entt) < current)) { + const auto pos = current++; + (std::get &>(cpools).swap_elements(std::get &>(cpools).data()[pos], entt), ...); + } + } + } + + void discard_if([[maybe_unused]] basic_registry &owner, const Entity entt) { + if constexpr(sizeof...(Owned) == 0) { + current.remove(entt); + } else { + if(const auto cpools = std::forward_as_tuple(owner.assure()...); std::get<0>(cpools).contains(entt) && (std::get<0>(cpools).index(entt) < current)) { + const auto pos = --current; + (std::get &>(cpools).swap_elements(std::get &>(cpools).data()[pos], entt), ...); + } + } + } + }; + + struct group_data { + std::size_t size; + std::unique_ptr group; + bool (*owned)(const id_type) ENTT_NOEXCEPT; + bool (*get)(const id_type) ENTT_NOEXCEPT; + bool (*exclude)(const id_type) ENTT_NOEXCEPT; + }; + + template + [[nodiscard]] auto &assure(const id_type id = type_hash::value()) { + static_assert(std::is_same_v>, "Non-decayed types not allowed"); + auto &&cpool = pools[id]; + + if(!cpool) { + cpool.reset(new storage_type{}); + cpool->bind(forward_as_any(*this)); + } + + ENTT_ASSERT(cpool->type() == type_id(), "Unexpected type"); + return static_cast &>(*cpool); + } + + template + [[nodiscard]] const auto &assure(const id_type id = type_hash::value()) const { + static_assert(std::is_same_v>, "Non-decayed types not allowed"); + + if(const auto it = pools.find(id); it != pools.cend()) { + ENTT_ASSERT(it->second->type() == type_id(), "Unexpected type"); + return static_cast &>(*it->second); + } + + static storage_type placeholder{}; + return placeholder; + } + + auto generate_identifier(const std::size_t pos) ENTT_NOEXCEPT { + ENTT_ASSERT(pos < entity_traits::to_entity(null), "No entities available"); + return entity_traits::combine(static_cast(pos), {}); + } + + auto recycle_identifier() ENTT_NOEXCEPT { + ENTT_ASSERT(free_list != null, "No entities available"); + const auto curr = entity_traits::to_entity(free_list); + free_list = entity_traits::combine(entity_traits::to_integral(entities[curr]), tombstone); + return (entities[curr] = entity_traits::combine(curr, entity_traits::to_integral(entities[curr]))); + } + + auto release_entity(const Entity entity, const typename entity_traits::version_type version) { + const typename entity_traits::version_type vers = version + (version == entity_traits::to_version(tombstone)); + entities[entity_traits::to_entity(entity)] = entity_traits::construct(entity_traits::to_integral(free_list), vers); + free_list = entity_traits::combine(entity_traits::to_integral(entity), tombstone); + return vers; + } + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Underlying version type. */ + using version_type = typename entity_traits::version_type; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using base_type = basic_common_type; + /*! @brief Context type. */ + using context = internal::registry_context; + + /*! @brief Default constructor. */ + basic_registry() + : pools{}, + groups{}, + entities{}, + free_list{tombstone}, + vars{} {} + + /** + * @brief Allocates enough memory upon construction to store `count` pools. + * @param count The number of pools to allocate memory for. + */ + basic_registry(const size_type count) + : pools{}, + groups{}, + entities{}, + free_list{tombstone}, + vars{} { + pools.reserve(count); + } + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_registry(basic_registry &&other) + : pools{std::move(other.pools)}, + groups{std::move(other.groups)}, + entities{std::move(other.entities)}, + free_list{other.free_list}, + vars{std::move(other.vars)} { + for(auto &&curr: pools) { + curr.second->bind(forward_as_any(*this)); + } + } + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This registry. + */ + basic_registry &operator=(basic_registry &&other) { + pools = std::move(other.pools); + groups = std::move(other.groups); + entities = std::move(other.entities); + free_list = other.free_list; + vars = std::move(other.vars); + + for(auto &&curr: pools) { + curr.second->bind(forward_as_any(*this)); + } + + return *this; + } + + /** + * @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]] auto storage() ENTT_NOEXCEPT { + return iterable_adaptor{internal::storage_proxy_iterator{pools.begin()}, internal::storage_proxy_iterator{pools.end()}}; + } + + /*! @copydoc storage */ + [[nodiscard]] auto storage() const ENTT_NOEXCEPT { + return iterable_adaptor{internal::storage_proxy_iterator{pools.cbegin()}, internal::storage_proxy_iterator{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 An iterator to the given storage if it's found, past the end + * iterator otherwise. + */ + [[nodiscard]] auto storage(const id_type id) { + return internal::storage_proxy_iterator{pools.find(id)}; + } + + /** + * @brief Finds the storage associated with a given name, if any. + * @param id Name used to map the storage within the registry. + * @return An iterator to the given storage if it's found, past the end + * iterator otherwise. + */ + [[nodiscard]] auto storage(const id_type id) const { + return internal::storage_proxy_iterator{pools.find(id)}; + } + + /** + * @brief Returns the storage for a given component type. + * @tparam Component Type of component of which to return the storage. + * @param id Optional name used to map the storage within the registry. + * @return The storage for the given component type. + */ + template + decltype(auto) storage(const id_type id = type_hash>::value()) { + if constexpr(std::is_const_v) { + return std::as_const(*this).template storage>(id); + } else { + return assure(id); + } + } + + /** + * @brief Returns the storage for a given component type. + * + * @warning + * If a storage for the given component doesn't exist yet, a temporary + * placeholder is returned instead. + * + * @tparam Component Type of component of which to return the storage. + * @param id Optional name used to map the storage within the registry. + * @return The storage for the given component type. + */ + template + decltype(auto) storage(const id_type id = type_hash>::value()) const { + return assure>(id); + } + + /** + * @brief Returns the number of entities created so far. + * @return Number of entities created so far. + */ + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return entities.size(); + } + + /** + * @brief Returns the number of entities still in use. + * @return Number of entities still in use. + */ + [[nodiscard]] size_type alive() const { + auto sz = entities.size(); + + for(auto curr = free_list; curr != null; --sz) { + curr = entities[entity_traits::to_entity(curr)]; + } + + return sz; + } + + /** + * @brief Increases the capacity (number of entities) of the registry. + * @param cap Desired capacity. + */ + void reserve(const size_type cap) { + entities.reserve(cap); + } + + /** + * @brief Returns the number of entities that a registry has currently + * allocated space for. + * @return Capacity of the registry. + */ + [[nodiscard]] size_type capacity() const ENTT_NOEXCEPT { + return entities.capacity(); + } + + /** + * @brief Checks whether the registry is empty (no entities still in use). + * @return True if the registry is empty, false otherwise. + */ + [[nodiscard]] bool empty() const { + return !alive(); + } + + /** + * @brief Direct access to the list of entities of a registry. + * + * The returned pointer is such that range `[data(), data() + size())` is + * always a valid range, even if the registry is empty. + * + * @warning + * This list contains both valid and destroyed entities and isn't suitable + * for direct use. + * + * @return A pointer to the array of entities. + */ + [[nodiscard]] const entity_type *data() const ENTT_NOEXCEPT { + return entities.data(); + } + + /** + * @brief Returns the head of the list of released entities. + * + * This function is intended for use in conjunction with `assign`.
+ * The returned entity has an invalid identifier in all cases. + * + * @return The head of the list of released entities. + */ + [[nodiscard]] entity_type released() const ENTT_NOEXCEPT { + return free_list; + } + + /** + * @brief Checks if an identifier refers to a valid entity. + * @param entity An identifier, either valid or not. + * @return True if the identifier is valid, false otherwise. + */ + [[nodiscard]] bool valid(const entity_type entity) const { + const auto pos = size_type(entity_traits::to_entity(entity)); + return (pos < entities.size() && entities[pos] == entity); + } + + /** + * @brief Returns the actual version for an identifier. + * @param entity A valid identifier. + * @return The version for the given identifier if valid, the tombstone + * version otherwise. + */ + [[nodiscard]] version_type current(const entity_type entity) const { + const auto pos = size_type(entity_traits::to_entity(entity)); + return entity_traits::to_version(pos < entities.size() ? entities[pos] : tombstone); + } + + /** + * @brief Creates a new entity or recycles a destroyed one. + * @return A valid identifier. + */ + [[nodiscard]] entity_type create() { + return (free_list == null) ? entities.emplace_back(generate_identifier(entities.size())) : recycle_identifier(); + } + + /** + * @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) { + const auto length = entities.size(); + + if(hint == null || hint == tombstone) { + return create(); + } else if(const auto req = entity_traits::to_entity(hint); !(req < length)) { + entities.resize(size_type(req) + 1u, null); + + for(auto pos = length; pos < req; ++pos) { + release_entity(generate_identifier(pos), {}); + } + + return (entities[req] = hint); + } else if(const auto curr = entity_traits::to_entity(entities[req]); req == curr) { + return create(); + } else { + auto *it = &free_list; + for(; entity_traits::to_entity(*it) != req; it = &entities[entity_traits::to_entity(*it)]) {} + *it = entity_traits::combine(curr, entity_traits::to_integral(*it)); + return (entities[req] = hint); + } + } + + /** + * @brief Assigns each element in a range an identifier. + * + * @sa create + * + * @tparam It Type of forward 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 + void create(It first, It last) { + for(; free_list != null && first != last; ++first) { + *first = recycle_identifier(); + } + + const auto length = entities.size(); + entities.resize(length + std::distance(first, last), null); + + for(auto pos = length; first != last; ++first, ++pos) { + *first = entities[pos] = generate_identifier(pos); + } + } + + /** + * @brief Assigns identifiers to an empty registry. + * + * This function is intended for use in conjunction with `data`, `size` and + * `destroyed`.
+ * Don't try to inject ranges of randomly generated entities nor the _wrong_ + * head for the list of destroyed entities. There is no guarantee that a + * registry will continue to work properly in this case. + * + * @warning + * There must be no entities still alive for this to work properly. + * + * @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 destroyed The head of the list of destroyed entities. + */ + template + void assign(It first, It last, const entity_type destroyed) { + ENTT_ASSERT(!alive(), "Entities still alive"); + entities.assign(first, last); + free_list = destroyed; + } + + /** + * @brief Releases an identifier. + * + * The version is updated and the identifier can be recycled at any time. + * + * @warning + * Attempting to use an invalid entity results in undefined behavior. + * + * @param entity A valid identifier. + * @return The version of the recycled entity. + */ + version_type release(const entity_type entity) { + return release(entity, static_cast(entity_traits::to_version(entity) + 1u)); + } + + /** + * @brief Releases an identifier. + * + * The suggested version or the valid version closest to the suggested one + * is used instead of the implicitly generated version. + * + * @sa release + * + * @param entity A valid identifier. + * @param version A desired version upon destruction. + * @return The version actually assigned to the entity. + */ + version_type release(const entity_type entity, const version_type version) { + ENTT_ASSERT(orphan(entity), "Non-orphan entity"); + return release_entity(entity, version); + } + + /** + * @brief Releases all identifiers in a range. + * + * @sa release + * + * @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 release(It first, It last) { + for(; first != last; ++first) { + release(*first); + } + } + + /** + * @brief Destroys an entity and releases its identifier. + * + * @sa release + * + * @warning + * Adding or removing components to an entity that is being destroyed can + * result in undefined behavior. Attempting to use an invalid entity results + * in undefined behavior. + * + * @param entity A valid identifier. + * @return The version of the recycled entity. + */ + version_type destroy(const entity_type entity) { + return destroy(entity, static_cast(entity_traits::to_version(entity) + 1u)); + } + + /** + * @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 entity A valid identifier. + * @param version A desired version upon destruction. + * @return The version actually assigned to the entity. + */ + version_type destroy(const entity_type entity, const version_type version) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + + for(size_type pos = pools.size(); pos; --pos) { + pools.begin()[pos - 1u].second->remove(entity); + } + + return release_entity(entity, version); + } + + /** + * @brief Destroys all entities in a range and releases their identifiers. + * + * @sa destroy + * + * @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 destroy(It first, It last) { + for(; first != last; ++first) { + destroy(*first); + } + } + + /** + * @brief Assigns the given component to an entity. + * + * The component must have a proper constructor or be of aggregate type. + * + * @warning + * Attempting to use an invalid entity or to assign a component to an entity + * that already owns it results in undefined behavior. + * + * @tparam Component Type of component to create. + * @tparam Args Types of arguments to use to construct the component. + * @param entity A valid identifier. + * @param args Parameters to use to initialize the component. + * @return A reference to the newly created component. + */ + template + decltype(auto) emplace(const entity_type entity, Args &&...args) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return assure().emplace(entity, std::forward(args)...); + } + + /** + * @brief Assigns each entity in a range the given component. + * + * @sa emplace + * + * @tparam Component Type of component to create. + * @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 value An instance of the component to assign. + */ + template + void insert(It first, It last, const Component &value = {}) { + ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity"); + assure().insert(first, last, value); + } + + /** + * @brief Assigns each entity in a range the given components. + * + * @sa emplace + * + * @tparam Component Type of component 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 components. + */ + template::value_type, Component>>> + void insert(EIt first, EIt last, CIt from) { + ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity"); + assure().insert(first, last, from); + } + + /** + * @brief Assigns or replaces the given component for an entity. + * + * @warning + * Attempting to use an invalid entity results in undefined behavior. + * + * @tparam Component Type of component to assign or replace. + * @tparam Args Types of arguments to use to construct the component. + * @param entity A valid identifier. + * @param args Parameters to use to initialize the component. + * @return A reference to the newly created component. + */ + template + decltype(auto) emplace_or_replace(const entity_type entity, Args &&...args) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + auto &cpool = assure(); + + return cpool.contains(entity) + ? cpool.patch(entity, [&args...](auto &...curr) { ((curr = Component{std::forward(args)...}), ...); }) + : cpool.emplace(entity, std::forward(args)...); + } + + /** + * @brief Patches the given component for an entity. + * + * The signature of the function should be equivalent to the following: + * + * @code{.cpp} + * void(Component &); + * @endcode + * + * @note + * Empty types aren't explicitly instantiated and therefore they are never + * returned. However, this function can be used to trigger an update signal + * for them. + * + * @warning + * Attempting to use an invalid entity or to patch a component of an entity + * that doesn't own it results in undefined behavior. + * + * @tparam Component Type of component to patch. + * @tparam Func Types of the function objects to invoke. + * @param entity A valid identifier. + * @param func Valid function objects. + * @return A reference to the patched component. + */ + template + decltype(auto) patch(const entity_type entity, Func &&...func) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return assure().patch(entity, std::forward(func)...); + } + + /** + * @brief Replaces the given component for an entity. + * + * The component must have a proper constructor or be of aggregate type. + * + * @warning + * Attempting to use an invalid entity or to replace a component of an + * entity that doesn't own it results in undefined behavior. + * + * @tparam Component Type of component to replace. + * @tparam Args Types of arguments to use to construct the component. + * @param entity A valid identifier. + * @param args Parameters to use to initialize the component. + * @return A reference to the component being replaced. + */ + template + decltype(auto) replace(const entity_type entity, Args &&...args) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return assure().patch(entity, [&args...](auto &...curr) { ((curr = Component{std::forward(args)...}), ...); }); + } + + /** + * @brief Removes the given components from an entity. + * + * @warning + * Attempting to use an invalid entity results in undefined behavior. + * + * @tparam Component Type of component to remove. + * @tparam Other Other types of components to remove. + * @param entity A valid identifier. + * @return The number of components actually removed. + */ + template + size_type remove(const entity_type entity) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return (assure().remove(entity) + ... + assure().remove(entity)); + } + + /** + * @brief Removes the given components from all the entities in a range. + * + * @sa remove + * + * @tparam Component Type of component to remove. + * @tparam Other Other types of components 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 components actually removed. + */ + template + size_type remove(It first, It last) { + if constexpr(sizeof...(Other) == 0u) { + ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity"); + return assure().remove(std::move(first), std::move(last)); + } else { + size_type count{}; + + for(auto cpools = std::forward_as_tuple(assure(), assure()...); first != last; ++first) { + ENTT_ASSERT(valid(*first), "Invalid entity"); + count += std::apply([entt = *first](auto &...curr) { return (curr.remove(entt) + ... + 0u); }, cpools); + } + + return count; + } + } + + /** + * @brief Erases the given components from an entity. + * + * @warning + * Attempting to use an invalid entity or to erase a component from an + * entity that doesn't own it results in undefined behavior. + * + * @tparam Component Types of components to erase. + * @tparam Other Other types of components to erase. + * @param entity A valid identifier. + */ + template + void erase(const entity_type entity) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + (assure().erase(entity), (assure().erase(entity), ...)); + } + + /** + * @brief Erases the given components from all the entities in a range. + * + * @sa erase + * + * @tparam Component Types of components to erase. + * @tparam Other Other types of components 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(sizeof...(Other) == 0u) { + ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity"); + assure().erase(std::move(first), std::move(last)); + } else { + for(auto cpools = std::forward_as_tuple(assure(), assure()...); first != last; ++first) { + ENTT_ASSERT(valid(*first), "Invalid entity"); + std::apply([entt = *first](auto &...curr) { (curr.erase(entt), ...); }, cpools); + } + } + } + + /** + * @brief Removes all tombstones from a registry or only the pools for the + * given components. + * @tparam Component Types of components for which to clear all tombstones. + */ + template + void compact() { + if constexpr(sizeof...(Component) == 0) { + for(auto &&curr: pools) { + curr.second->compact(); + } + } else { + (assure().compact(), ...); + } + } + + /** + * @brief Checks if an entity has all the given components. + * + * @warning + * Attempting to use an invalid entity results in undefined behavior. + * + * @tparam Component Components for which to perform the check. + * @param entity A valid identifier. + * @return True if the entity has all the components, false otherwise. + */ + template + [[nodiscard]] bool all_of(const entity_type entity) const { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return (assure>().contains(entity) && ...); + } + + /** + * @brief Checks if an entity has at least one of the given components. + * + * @warning + * Attempting to use an invalid entity results in undefined behavior. + * + * @tparam Component Components for which to perform the check. + * @param entity A valid identifier. + * @return True if the entity has at least one of the given components, + * false otherwise. + */ + template + [[nodiscard]] bool any_of(const entity_type entity) const { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return (assure>().contains(entity) || ...); + } + + /** + * @brief Returns references to the given components for an entity. + * + * @warning + * Attempting to use an invalid entity or to get a component from an entity + * that doesn't own it results in undefined behavior. + * + * @tparam Component Types of components to get. + * @param entity A valid identifier. + * @return References to the components owned by the entity. + */ + template + [[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entity) const { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return view().template get(entity); + } + + /*! @copydoc get */ + template + [[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entity) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return view().template get(entity); + } + + /** + * @brief Returns a reference to the given component for an entity. + * + * In case the entity doesn't own the component, the parameters provided are + * used to construct it. + * + * @warning + * Attempting to use an invalid entity results in undefined behavior. + * + * @tparam Component Type of component to get. + * @tparam Args Types of arguments to use to construct the component. + * @param entity A valid identifier. + * @param args Parameters to use to initialize the component. + * @return Reference to the component owned by the entity. + */ + template + [[nodiscard]] decltype(auto) get_or_emplace(const entity_type entity, Args &&...args) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + auto &cpool = assure(); + return cpool.contains(entity) ? cpool.get(entity) : cpool.emplace(entity, std::forward(args)...); + } + + /** + * @brief Returns pointers to the given components for an entity. + * + * @warning + * Attempting to use an invalid entity results in undefined behavior. + * + * @note + * The registry retains ownership of the pointed-to components. + * + * @tparam Component Types of components to get. + * @param entity A valid identifier. + * @return Pointers to the components owned by the entity. + */ + template + [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entity) const { + ENTT_ASSERT(valid(entity), "Invalid entity"); + + if constexpr(sizeof...(Component) == 1) { + const auto &cpool = assure...>(); + return cpool.contains(entity) ? std::addressof(cpool.get(entity)) : nullptr; + } else { + return std::make_tuple(try_get(entity)...); + } + } + + /*! @copydoc try_get */ + template + [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entity) { + if constexpr(sizeof...(Component) == 1) { + return (const_cast(std::as_const(*this).template try_get(entity)), ...); + } else { + return std::make_tuple(try_get(entity)...); + } + } + + /** + * @brief Clears a whole registry or the pools for the given components. + * @tparam Component Types of components to remove from their entities. + */ + template + void clear() { + if constexpr(sizeof...(Component) == 0) { + for(auto &&curr: pools) { + curr.second->clear(); + } + + each([this](const auto entity) { this->release(entity); }); + } else { + (assure().clear(), ...); + } + } + + /** + * @brief Iterates all the entities that are still in use. + * + * The signature of the function should be equivalent to the following: + * + * @code{.cpp} + * void(const Entity); + * @endcode + * + * It's not defined whether entities created during iteration are returned. + * + * @tparam Func Type of the function object to invoke. + * @param func A valid function object. + */ + template + void each(Func func) const { + if(free_list == null) { + for(auto pos = entities.size(); pos; --pos) { + func(entities[pos - 1]); + } + } else { + for(auto pos = entities.size(); pos; --pos) { + if(const auto entity = entities[pos - 1]; entity_traits::to_entity(entity) == (pos - 1)) { + func(entity); + } + } + } + } + + /** + * @brief Checks if an entity has components assigned. + * @param entity A valid identifier. + * @return True if the entity has no components assigned, false otherwise. + */ + [[nodiscard]] bool orphan(const entity_type entity) const { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return std::none_of(pools.cbegin(), pools.cend(), [entity](auto &&curr) { return curr.second->contains(entity); }); + } + + /** + * @brief Returns a sink object for the given component. + * + * Use this function to receive notifications whenever a new instance of the + * given component 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 component to the entity. + * + * @sa sink + * + * @tparam Component Type of component of which to get the sink. + * @return A temporary sink object. + */ + template + [[nodiscard]] auto on_construct() { + return assure().on_construct(); + } + + /** + * @brief Returns a sink object for the given component. + * + * Use this function to receive notifications whenever an instance of the + * given component 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 component. + * + * @sa sink + * + * @tparam Component Type of component of which to get the sink. + * @return A temporary sink object. + */ + template + [[nodiscard]] auto on_update() { + return assure().on_update(); + } + + /** + * @brief Returns a sink object for the given component. + * + * Use this function to receive notifications whenever an instance of the + * given component 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 component from the entity. + * + * @sa sink + * + * @tparam Component Type of component of which to get the sink. + * @return A temporary sink object. + */ + template + [[nodiscard]] auto on_destroy() { + return assure().on_destroy(); + } + + /** + * @brief Returns a view for the given components. + * + * Views are created on the fly and share with the registry its internal + * data structures. Feel free to discard them after the use.
+ * Creating and destroying a view is an incredibly cheap operation. As a + * rule of thumb, storing a view should never be an option. + * + * @tparam Component Type of component used to construct the view. + * @tparam Other Other types of components used to construct the view. + * @tparam Exclude Types of components used to filter the view. + * @return A newly created view. + */ + template + [[nodiscard]] basic_view, std::add_const_t...>, exclude_t> view(exclude_t = {}) const { + return {assure>(), assure>()..., assure()...}; + } + + /*! @copydoc view */ + template + [[nodiscard]] basic_view, exclude_t> view(exclude_t = {}) { + return {assure>(), assure>()..., assure()...}; + } + + /** + * @brief Returns a group for the given components. + * + * Groups are created on the fly and share with the registry its internal + * data structures. Feel free to discard them after the use.
+ * Creating and destroying a group is an incredibly cheap operation. As a + * rule of thumb, storing a group should never be an option. + * + * Groups support exclusion lists and can own types of components. The more + * types are owned by a group, the faster it is to iterate entities and + * components.
+ * However, groups also affect some features of the registry such as the + * creation and destruction of components. + * + * @note + * Pools of components that are owned by a group cannot be sorted anymore. + * The group takes the ownership of the pools and arrange components so as + * to iterate them as fast as possible. + * + * @tparam Owned Types of components owned by the group. + * @tparam Get Types of components observed by the group. + * @tparam Exclude Types of components used to filter the group. + * @return A newly created group. + */ + template + [[nodiscard]] basic_group, get_t, exclude_t> group(get_t, exclude_t = {}) { + static_assert(sizeof...(Owned) + sizeof...(Get) > 0, "Exclusion-only groups are not supported"); + static_assert(sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude) > 1, "Single component groups are not allowed"); + + using handler_type = group_handler...>, get_t...>, std::remove_const_t...>; + + const auto cpools = std::forward_as_tuple(assure>()..., assure>()...); + constexpr auto size = sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude); + handler_type *handler = nullptr; + + auto it = std::find_if(groups.cbegin(), groups.cend(), [size](const auto &gdata) { + return gdata.size == size + && (gdata.owned(type_hash>::value()) && ...) + && (gdata.get(type_hash>::value()) && ...) + && (gdata.exclude(type_hash::value()) && ...); + }); + + if(it != groups.cend()) { + handler = static_cast(it->group.get()); + } else { + group_data candidate = { + size, + {new handler_type{}, [](void *instance) { delete static_cast(instance); }}, + []([[maybe_unused]] const id_type ctype) ENTT_NOEXCEPT { return ((ctype == type_hash>::value()) || ...); }, + []([[maybe_unused]] const id_type ctype) ENTT_NOEXCEPT { return ((ctype == type_hash>::value()) || ...); }, + []([[maybe_unused]] const id_type ctype) ENTT_NOEXCEPT { return ((ctype == type_hash::value()) || ...); }, + }; + + handler = static_cast(candidate.group.get()); + + const void *maybe_valid_if = nullptr; + const void *discard_if = nullptr; + + if constexpr(sizeof...(Owned) == 0) { + groups.push_back(std::move(candidate)); + } else { + [[maybe_unused]] auto has_conflict = [size](const auto &gdata) { + const auto overlapping = (0u + ... + gdata.owned(type_hash>::value())); + const auto sz = overlapping + (0u + ... + gdata.get(type_hash>::value())) + (0u + ... + gdata.exclude(type_hash::value())); + return !overlapping || ((sz == size) || (sz == gdata.size)); + }; + + ENTT_ASSERT(std::all_of(groups.cbegin(), groups.cend(), std::move(has_conflict)), "Conflicting groups"); + + const auto next = std::find_if_not(groups.cbegin(), groups.cend(), [size](const auto &gdata) { + return !(0u + ... + gdata.owned(type_hash>::value())) || (size > gdata.size); + }); + + const auto prev = std::find_if(std::make_reverse_iterator(next), groups.crend(), [](const auto &gdata) { + return (0u + ... + gdata.owned(type_hash>::value())); + }); + + maybe_valid_if = (next == groups.cend() ? maybe_valid_if : next->group.get()); + discard_if = (prev == groups.crend() ? discard_if : prev->group.get()); + groups.insert(next, std::move(candidate)); + } + + (on_construct>().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if>>(*handler), ...); + (on_construct>().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if>>(*handler), ...); + (on_destroy().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if>(*handler), ...); + + (on_destroy>().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...); + (on_destroy>().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...); + (on_construct().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...); + + if constexpr(sizeof...(Owned) == 0) { + for(const auto entity: view(exclude)) { + handler->current.emplace(entity); + } + } else { + // we cannot iterate backwards because we want to leave behind valid entities in case of owned types + for(auto *first = std::get<0>(cpools).data(), *last = first + std::get<0>(cpools).size(); first != last; ++first) { + handler->template maybe_valid_if...>>>(*this, *first); + } + } + } + + return {handler->current, std::get> &>(cpools)..., std::get> &>(cpools)...}; + } + + /*! @copydoc group */ + template + [[nodiscard]] basic_group...>, get_t...>, exclude_t> group_if_exists(get_t, exclude_t = {}) const { + auto it = std::find_if(groups.cbegin(), groups.cend(), [](const auto &gdata) { + return gdata.size == (sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude)) + && (gdata.owned(type_hash>::value()) && ...) + && (gdata.get(type_hash>::value()) && ...) + && (gdata.exclude(type_hash::value()) && ...); + }); + + if(it == groups.cend()) { + return {}; + } else { + using handler_type = group_handler...>, get_t...>, std::remove_const_t...>; + return {static_cast(it->group.get())->current, assure>()..., assure>()...}; + } + } + + /*! @copydoc group */ + template + [[nodiscard]] basic_group, get_t<>, exclude_t> group(exclude_t = {}) { + return group(get_t<>{}, exclude); + } + + /*! @copydoc group */ + template + [[nodiscard]] basic_group...>, get_t<>, exclude_t> group_if_exists(exclude_t = {}) const { + return group_if_exists...>(get_t<>{}, exclude); + } + + /** + * @brief Checks whether the given components belong to any group. + * @tparam Component Types of components in which one is interested. + * @return True if the pools of the given components are _free_, false + * otherwise. + */ + template + [[nodiscard]] bool owned() const { + return std::any_of(groups.cbegin(), groups.cend(), [](auto &&gdata) { return (gdata.owned(type_hash>::value()) || ...); }); + } + + /** + * @brief Checks whether a group can be sorted. + * @tparam Owned Types of components owned by the group. + * @tparam Get Types of components observed by the group. + * @tparam Exclude Types of components used to filter the group. + * @return True if the group can be sorted, false otherwise. + */ + template + [[nodiscard]] bool sortable(const basic_group, get_t, exclude_t> &) ENTT_NOEXCEPT { + constexpr auto size = sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude); + auto pred = [size](const auto &gdata) { return (0u + ... + gdata.owned(type_hash>::value())) && (size < gdata.size); }; + return std::find_if(groups.cbegin(), groups.cend(), std::move(pred)) == groups.cend(); + } + + /** + * @brief Sorts the elements of a given component. + * + * The order remains valid until a component of the given type is assigned + * to or removed from an entity.
+ * 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 Component &, const Component &); + * @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 components owned by a group cannot be sorted. + * + * @tparam Component Type of components 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(std::is_invocable_v) { + auto comp = [&cpool, compare = std::move(compare)](const auto lhs, const auto rhs) { return compare(std::as_const(cpool.get(lhs)), std::as_const(cpool.get(rhs))); }; + cpool.sort(std::move(comp), std::move(algo), std::forward(args)...); + } else { + cpool.sort(std::move(compare), std::move(algo), std::forward(args)...); + } + } + + /** + * @brief Sorts two pools of components in the same way. + * + * Being `To` and `From` the two sets, after invoking this function an + * iterator for `To` returns elements according to the following rules: + * + * * All entities in `To` that are also in `From` are returned first + * according to the order they have in `From`. + * * All entities in `To` that are not in `From` are returned in no + * particular order after all the other entities. + * + * Any subsequent change to `From` won't affect the order in `To`. + * + * @warning + * Pools of components owned by a group cannot be sorted. + * + * @tparam To Type of components to sort. + * @tparam From Type of components to use to sort. + */ + template + void sort() { + ENTT_ASSERT(!owned(), "Cannot sort owned storage"); + assure().respect(assure()); + } + + /** + * @brief Returns the context object, that is, a general purpose container. + * @return The context object, that is, a general purpose container. + */ + context &ctx() ENTT_NOEXCEPT { + return vars; + } + + /*! @copydoc ctx */ + const context &ctx() const ENTT_NOEXCEPT { + return vars; + } + +private: + dense_map, identity> pools; + std::vector groups; + std::vector entities; + entity_type free_list; + context vars; +}; + +} // namespace entt + +#endif + +// #include "entity/runtime_view.hpp" +#ifndef ENTT_ENTITY_RUNTIME_VIEW_HPP +#define ENTT_ENTITY_RUNTIME_VIEW_HPP + +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "entity.hpp" + +// #include "fwd.hpp" + +// #include "sparse_set.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +class runtime_view_iterator final { + using iterator_type = typename Set::iterator; + + [[nodiscard]] bool valid() const { + return (!tombstone_check || *it != tombstone) + && std::all_of(++pools->begin(), pools->end(), [entt = *it](const auto *curr) { return curr->contains(entt); }) + && std::none_of(filter->cbegin(), filter->cend(), [entt = *it](const auto *curr) { return curr && curr->contains(entt); }); + } + +public: + using difference_type = typename iterator_type::difference_type; + using value_type = typename iterator_type::value_type; + using pointer = typename iterator_type::pointer; + using reference = typename iterator_type::reference; + using iterator_category = std::bidirectional_iterator_tag; + + runtime_view_iterator() ENTT_NOEXCEPT + : pools{}, + filter{}, + it{}, + tombstone_check{} {} + + runtime_view_iterator(const std::vector &cpools, const std::vector &ignore, iterator_type curr) ENTT_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++() { + while(++it != (*pools)[0]->end() && !valid()) {} + return *this; + } + + runtime_view_iterator operator++(int) { + runtime_view_iterator orig = *this; + return ++(*this), orig; + } + + runtime_view_iterator &operator--() { + while(--it != (*pools)[0]->begin() && !valid()) {} + return *this; + } + + runtime_view_iterator operator--(int) { + runtime_view_iterator orig = *this; + return operator--(), orig; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return it.operator->(); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return *operator->(); + } + + [[nodiscard]] bool operator==(const runtime_view_iterator &other) const ENTT_NOEXCEPT { + return it == other.it; + } + + [[nodiscard]] bool operator!=(const runtime_view_iterator &other) const ENTT_NOEXCEPT { + return !(*this == other); + } + +private: + const std::vector *pools; + const std::vector *filter; + iterator_type it; + bool tombstone_check; +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Runtime view implementation. + * + * Primary template isn't defined on purpose. All the specializations give a + * compile-time error, but for a few reasonable cases. + */ +template +struct basic_runtime_view; + +/** + * @brief Generic runtime view. + * + * Runtime views iterate over those entities that have at least all the given + * components in their bags. During initialization, a runtime view looks at the + * number of entities available for each component and picks up a reference to + * the smallest set of candidate entities in order to get a performance boost + * when iterate.
+ * Order of elements during iterations are highly dependent on the order of the + * underlying data structures. See sparse_set and its specializations for more + * details. + * + * @b Important + * + * Iterators aren't invalidated if: + * + * * New instances of the given components are created and assigned to entities. + * * The entity currently pointed is modified (as an example, if one of the + * given components is removed from the entity to which the iterator points). + * * The entity currently pointed is destroyed. + * + * In all the other cases, modifying the pools of the given components in any + * way invalidates all the iterators and using them results in undefined + * behavior. + * + * @note + * Views share references to the underlying data structures of the registry that + * generated them. Therefore any change to the entities and to the components + * made by means of the registry are immediately reflected by the views, unless + * a pool was missing when the view was built (in this case, the view won't + * have a valid reference and won't be updated accordingly). + * + * @warning + * Lifetime of a view must not overcome that of the registry that generated it. + * In any other case, attempting to use a view results in undefined behavior. + * + * @tparam Entity A valid entity type (see entt_traits for more details). + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +struct basic_runtime_view> { + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using base_type = basic_sparse_set; + /*! @brief Bidirectional iterator type. */ + using iterator = internal::runtime_view_iterator; + + /*! @brief Default constructor to use to create empty, invalid views. */ + basic_runtime_view() ENTT_NOEXCEPT + : pools{}, + filter{} {} + + /** + * @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(const base_type &base) { + if(pools.empty() || !(base.size() < pools[0u]->size())) { + pools.push_back(&base); + } else { + pools.push_back(std::exchange(pools[0u], &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(const base_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{} : pools.front()->size(); + } + + /** + * @brief Returns an iterator to the first entity that has the given + * components. + * + * The returned iterator points to the first entity that has the given + * components. If the view is empty, the returned iterator will be equal to + * `end()`. + * + * @return An iterator to the first entity that has the given components. + */ + [[nodiscard]] iterator begin() const { + return pools.empty() ? iterator{} : iterator{pools, filter, pools[0]->begin()}; + } + + /** + * @brief Returns an iterator that is past the last entity that has the + * given components. + * + * The returned iterator points to the entity following the last entity that + * has the given components. Attempting to dereference the returned iterator + * results in undefined behavior. + * + * @return An iterator to the entity following the last entity that has the + * given components. + */ + [[nodiscard]] iterator end() const { + return pools.empty() ? iterator{} : iterator{pools, filter, pools[0]->end()}; + } + + /** + * @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() + && std::all_of(pools.cbegin(), pools.cend(), [entt](const auto *curr) { return curr->contains(entt); }) + && std::none_of(filter.cbegin(), filter.cend(), [entt](const auto *curr) { return curr && curr->contains(entt); }); + } + + /** + * @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. To get the components, users can use the registry with + * which the view was built.
+ * 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: + std::vector pools; + std::vector filter; +}; + +} // namespace entt + +#endif + +// #include "entity/sigh_storage_mixin.hpp" +#ifndef ENTT_ENTITY_SIGH_STORAGE_MIXIN_HPP +#define ENTT_ENTITY_SIGH_STORAGE_MIXIN_HPP + +#include +// #include "../config/config.h" + +// #include "../core/any.hpp" + +// #include "../signal/sigh.hpp" + +// #include "fwd.hpp" + + +namespace entt { + +/** + * @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 The type of the underlying storage. + */ +template +class sigh_storage_mixin final: public Type { + using basic_iterator = typename Type::basic_iterator; + + template + void notify_destruction(basic_iterator first, basic_iterator last, Func func) { + ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); + + for(; first != last; ++first) { + const auto entt = *first; + destruction.publish(*owner, entt); + const auto it = Type::find(entt); + func(it, it + 1u); + } + } + + void swap_and_pop(basic_iterator first, basic_iterator last) final { + notify_destruction(std::move(first), std::move(last), [this](auto... args) { Type::swap_and_pop(args...); }); + } + + void in_place_pop(basic_iterator first, basic_iterator last) final { + notify_destruction(std::move(first), std::move(last), [this](auto... args) { Type::in_place_pop(args...); }); + } + + basic_iterator try_emplace(const typename Type::entity_type entt, const bool force_back, const void *value) final { + ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); + Type::try_emplace(entt, force_back, value); + construction.publish(*owner, entt); + return Type::find(entt); + } + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = typename Type::entity_type; + + /*! @brief Inherited constructors. */ + using Type::Type; + + /** + * @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() ENTT_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() ENTT_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() ENTT_NOEXCEPT { + return sink{destruction}; + } + + /** + * @brief Assigns entities to a storage. + * @tparam Args Types of arguments to use to construct the object. + * @param entt A valid identifier. + * @param args Parameters to use to initialize the object. + * @return A reference to the newly created object. + */ + template + decltype(auto) emplace(const entity_type entt, Args &&...args) { + ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); + Type::emplace(entt, std::forward(args)...); + construction.publish(*owner, entt); + return this->get(entt); + } + + /** + * @brief Patches the given instance for an entity. + * @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) { + ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); + Type::patch(entt, std::forward(func)...); + update.publish(*owner, entt); + return this->get(entt); + } + + /** + * @brief Assigns entities to a storage. + * @tparam It Type of input iterator. + * @tparam Args Types of arguments to use to construct the objects assigned + * to the entities. + * @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 initialize the objects assigned to the + * entities. + */ + template + void insert(It first, It last, Args &&...args) { + ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); + Type::insert(first, last, std::forward(args)...); + + for(auto it = construction.empty() ? last : first; it != last; ++it) { + construction.publish(*owner, *it); + } + } + + /** + * @brief Forwards variables to mixins, if any. + * @param value A variable wrapped in an opaque container. + */ + void bind(any value) ENTT_NOEXCEPT final { + auto *reg = any_cast>(&value); + owner = reg ? reg : owner; + Type::bind(std::move(value)); + } + +private: + sigh &, const entity_type)> construction{}; + sigh &, const entity_type)> destruction{}; + sigh &, const entity_type)> update{}; + basic_registry *owner{}; +}; + +} // namespace entt + +#endif + +// #include "entity/snapshot.hpp" +#ifndef ENTT_ENTITY_SNAPSHOT_HPP +#define ENTT_ENTITY_SNAPSHOT_HPP + +#include +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../container/dense_map.hpp" + +// #include "../core/type_traits.hpp" + +// #include "component.hpp" + +// #include "entity.hpp" + +// #include "fwd.hpp" + +// #include "registry.hpp" + + +namespace entt { + +/** + * @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 components of interest.
+ * This type can be used in both cases if provided with a correctly configured + * output archive. + * + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +class basic_snapshot { + using entity_traits = entt_traits; + + template + void get(Archive &archive, std::size_t sz, It first, It last) const { + const auto view = reg->template view>(); + archive(typename entity_traits::entity_type(sz)); + + while(first != last) { + const auto entt = *(first++); + + if(reg->template all_of(entt)) { + std::apply(archive, std::tuple_cat(std::make_tuple(entt), view.get(entt))); + } + } + } + + template + void component(Archive &archive, It first, It last, std::index_sequence) const { + std::array size{}; + auto begin = first; + + while(begin != last) { + const auto entt = *(begin++); + ((reg->template all_of(entt) ? ++size[Index] : 0u), ...); + } + + (get(archive, size[Index], first, last), ...); + } + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + + /** + * @brief Constructs an instance that is bound to a given registry. + * @param source A valid reference to a registry. + */ + basic_snapshot(const basic_registry &source) ENTT_NOEXCEPT + : reg{&source} {} + + /*! @brief Default move constructor. */ + basic_snapshot(basic_snapshot &&) ENTT_NOEXCEPT = default; + + /*! @brief Default move assignment operator. @return This snapshot. */ + basic_snapshot &operator=(basic_snapshot &&) ENTT_NOEXCEPT = default; + + /** + * @brief Puts aside all the entities from the underlying registry. + * + * Entities are serialized along with their versions. Destroyed entities are + * taken in consideration as well by this function. + * + * @tparam Archive Type of output archive. + * @param archive A valid reference to an output archive. + * @return An object of this type to continue creating the snapshot. + */ + template + const basic_snapshot &entities(Archive &archive) const { + const auto sz = reg->size(); + + archive(typename entity_traits::entity_type(sz + 1u)); + archive(reg->released()); + + for(auto first = reg->data(), last = first + sz; first != last; ++first) { + archive(*first); + } + + return *this; + } + + /** + * @brief Puts aside the given components. + * + * Each instance is serialized together with the entity to which it belongs. + * Entities are serialized along with their versions. + * + * @tparam Component Types of components to serialize. + * @tparam Archive Type of output archive. + * @param archive A valid reference to an output archive. + * @return An object of this type to continue creating the snapshot. + */ + template + const basic_snapshot &component(Archive &archive) const { + if constexpr(sizeof...(Component) == 1u) { + const auto view = reg->template view(); + (component(archive, view.rbegin(), view.rend()), ...); + return *this; + } else { + (component(archive), ...); + return *this; + } + } + + /** + * @brief Puts aside the given components for the entities in a range. + * + * Each instance is serialized together with the entity to which it belongs. + * Entities are serialized along with their versions. + * + * @tparam Component Types of components to serialize. + * @tparam Archive Type of output archive. + * @tparam It Type of input iterator. + * @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. + * @return An object of this type to continue creating the snapshot. + */ + template + const basic_snapshot &component(Archive &archive, It first, It last) const { + component(archive, first, last, std::index_sequence_for{}); + return *this; + } + +private: + const basic_registry *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 Entity A valid entity type (see entt_traits for more details). + */ +template +class basic_snapshot_loader { + using entity_traits = entt_traits; + + template + void assign(Archive &archive) const { + typename entity_traits::entity_type length{}; + entity_type entt; + + archive(length); + + if constexpr(ignore_as_empty_v) { + while(length--) { + archive(entt); + const auto entity = reg->valid(entt) ? entt : reg->create(entt); + ENTT_ASSERT(entity == entt, "Entity not available for use"); + reg->template emplace(entt); + } + } else { + Type instance; + + while(length--) { + archive(entt, instance); + const auto entity = reg->valid(entt) ? entt : reg->create(entt); + ENTT_ASSERT(entity == entt, "Entity not available for use"); + reg->template emplace(entt, std::move(instance)); + } + } + } + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + + /** + * @brief Constructs an instance that is bound to a given registry. + * @param source A valid reference to a registry. + */ + basic_snapshot_loader(basic_registry &source) ENTT_NOEXCEPT + : reg{&source} { + // restoring a snapshot as a whole requires a clean registry + ENTT_ASSERT(reg->empty(), "Registry must be empty"); + } + + /*! @brief Default move constructor. */ + basic_snapshot_loader(basic_snapshot_loader &&) ENTT_NOEXCEPT = default; + + /*! @brief Default move assignment operator. @return This loader. */ + basic_snapshot_loader &operator=(basic_snapshot_loader &&) ENTT_NOEXCEPT = default; + + /** + * @brief Restores entities that were in use during serialization. + * + * This function restores the entities that were in use during serialization + * and gives them the versions they originally had. + * + * @tparam Archive Type of input archive. + * @param archive A valid reference to an input archive. + * @return A valid loader to continue restoring data. + */ + template + const basic_snapshot_loader &entities(Archive &archive) const { + typename entity_traits::entity_type length{}; + + archive(length); + std::vector all(length); + + for(std::size_t pos{}; pos < length; ++pos) { + archive(all[pos]); + } + + reg->assign(++all.cbegin(), all.cend(), all[0u]); + + return *this; + } + + /** + * @brief Restores components and assigns them to the right entities. + * + * The template parameter list must be exactly the same used during + * serialization. In the event that the entity to which the component is + * assigned doesn't exist yet, the loader will take care to create it with + * the version it originally had. + * + * @tparam Component Types of components to restore. + * @tparam Archive Type of input archive. + * @param archive A valid reference to an input archive. + * @return A valid loader to continue restoring data. + */ + template + const basic_snapshot_loader &component(Archive &archive) const { + (assign(archive), ...); + return *this; + } + + /** + * @brief Destroys those entities that have no components. + * + * In case all the entities were serialized but only part of the components + * was saved, it could happen that some of the entities have no components + * once restored.
+ * This functions helps to identify and destroy those entities. + * + * @return A valid loader to continue restoring data. + */ + const basic_snapshot_loader &orphans() const { + reg->each([this](const auto entt) { + if(reg->orphan(entt)) { + reg->release(entt); + } + }); + + return *this; + } + +private: + basic_registry *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 applications with + * the requirement of transferring somehow parts of the representation side to + * side. + * + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +class basic_continuous_loader { + using entity_traits = entt_traits; + + void destroy(Entity entt) { + if(const auto it = remloc.find(entt); it == remloc.cend()) { + const auto local = reg->create(); + remloc.emplace(entt, std::make_pair(local, true)); + reg->destroy(local); + } + } + + void restore(Entity entt) { + const auto it = remloc.find(entt); + + if(it == remloc.cend()) { + const auto local = reg->create(); + remloc.emplace(entt, std::make_pair(local, true)); + } else { + if(!reg->valid(remloc[entt].first)) { + remloc[entt].first = reg->create(); + } + + // set the dirty flag + remloc[entt].second = true; + } + } + + template + auto update(int, Container &container) -> decltype(typename Container::mapped_type{}, void()) { + // map like container + Container other; + + for(auto &&pair: container) { + using first_type = std::remove_const_t::first_type>; + using second_type = typename std::decay_t::second_type; + + if constexpr(std::is_same_v && std::is_same_v) { + other.emplace(map(pair.first), map(pair.second)); + } else if constexpr(std::is_same_v) { + other.emplace(map(pair.first), std::move(pair.second)); + } else { + static_assert(std::is_same_v, "Neither the key nor the value are of entity type"); + other.emplace(std::move(pair.first), map(pair.second)); + } + } + + using std::swap; + swap(container, other); + } + + template + auto update(char, Container &container) -> decltype(typename Container::value_type{}, void()) { + // vector like container + static_assert(std::is_same_v, "Invalid value type"); + + for(auto &&entt: container) { + entt = map(entt); + } + } + + template + void update([[maybe_unused]] Other &instance, [[maybe_unused]] Member Type::*member) { + if constexpr(!std::is_same_v) { + return; + } else if constexpr(std::is_same_v) { + instance.*member = map(instance.*member); + } else { + // maybe a container? let's try... + update(0, instance.*member); + } + } + + template + void remove_if_exists() { + for(auto &&ref: remloc) { + const auto local = ref.second.first; + + if(reg->valid(local)) { + reg->template remove(local); + } + } + } + + template + void assign(Archive &archive, [[maybe_unused]] Member Type::*...member) { + typename entity_traits::entity_type length{}; + entity_type entt; + + archive(length); + + if constexpr(ignore_as_empty_v) { + while(length--) { + archive(entt); + restore(entt); + reg->template emplace_or_replace(map(entt)); + } + } else { + Other instance; + + while(length--) { + archive(entt, instance); + (update(instance, member), ...); + restore(entt); + reg->template emplace_or_replace(map(entt), std::move(instance)); + } + } + } + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + + /** + * @brief Constructs an instance that is bound to a given registry. + * @param source A valid reference to a registry. + */ + basic_continuous_loader(basic_registry &source) ENTT_NOEXCEPT + : reg{&source} {} + + /*! @brief Default move constructor. */ + basic_continuous_loader(basic_continuous_loader &&) = default; + + /*! @brief Default move assignment operator. @return This loader. */ + basic_continuous_loader &operator=(basic_continuous_loader &&) = default; + + /** + * @brief Restores entities that were in use during serialization. + * + * This function restores the entities that were in use during serialization + * and creates local counterparts for them if required. + * + * @tparam Archive Type of input archive. + * @param archive A valid reference to an input archive. + * @return A non-const reference to this loader. + */ + template + basic_continuous_loader &entities(Archive &archive) { + typename entity_traits::entity_type length{}; + entity_type entt{}; + + archive(length); + // discards the head of the list of destroyed entities + archive(entt); + + for(std::size_t pos{}, last = length - 1u; pos < last; ++pos) { + archive(entt); + + if(const auto entity = entity_traits::to_entity(entt); entity == pos) { + restore(entt); + } else { + destroy(entt); + } + } + + return *this; + } + + /** + * @brief Restores components and assigns them to the right entities. + * + * The template parameter list must be exactly the same used during + * serialization. In the event that the entity to which the component is + * assigned doesn't exist yet, the loader will take care to create a local + * counterpart for it.
+ * Members can be either data members of type entity_type or containers of + * entities. In both cases, the loader will visit them and update the + * entities by replacing each one with its local counterpart. + * + * @tparam Component Type of component to restore. + * @tparam Archive Type of input archive. + * @tparam Type Types of components to update with local counterparts. + * @tparam Member Types of members to update with their local counterparts. + * @param archive A valid reference to an input archive. + * @param member Members to update with their local counterparts. + * @return A non-const reference to this loader. + */ + template + basic_continuous_loader &component(Archive &archive, Member Type::*...member) { + (remove_if_exists(), ...); + (assign(archive, member...), ...); + return *this; + } + + /** + * @brief Helps to purge entities that no longer have a conterpart. + * + * Users should invoke this member function after restoring each snapshot, + * unless they know exactly what they are doing. + * + * @return A non-const reference to this loader. + */ + basic_continuous_loader &shrink() { + auto it = remloc.begin(); + + while(it != remloc.cend()) { + const auto local = it->second.first; + bool &dirty = it->second.second; + + if(dirty) { + dirty = false; + ++it; + } else { + if(reg->valid(local)) { + reg->destroy(local); + } + + it = remloc.erase(it); + } + } + + return *this; + } + + /** + * @brief Destroys those entities that have no components. + * + * In case all the entities were serialized but only part of the components + * was saved, it could happen that some of the entities have no components + * once restored.
+ * This functions helps to identify and destroy those entities. + * + * @return A non-const reference to this loader. + */ + basic_continuous_loader &orphans() { + reg->each([this](const auto entt) { + if(reg->orphan(entt)) { + reg->release(entt); + } + }); + + 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 ENTT_NOEXCEPT { + return (remloc.find(entt) != remloc.cend()); + } + + /** + * @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 ENTT_NOEXCEPT { + const auto it = remloc.find(entt); + entity_type other = null; + + if(it != remloc.cend()) { + other = it->second.first; + } + + return other; + } + +private: + dense_map> remloc; + basic_registry *reg; +}; + +} // namespace entt + +#endif + +// #include "entity/sparse_set.hpp" +#ifndef ENTT_ENTITY_SPARSE_SET_HPP +#define ENTT_ENTITY_SPARSE_SET_HPP + +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/algorithm.hpp" + +// #include "../core/any.hpp" + +// #include "../core/memory.hpp" + +// #include "../core/type_info.hpp" + +// #include "entity.hpp" + +// #include "fwd.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct sparse_set_iterator final { + using value_type = typename Container::value_type; + using pointer = typename Container::const_pointer; + using reference = typename Container::const_reference; + using difference_type = typename Container::difference_type; + using iterator_category = std::random_access_iterator_tag; + + sparse_set_iterator() ENTT_NOEXCEPT + : packed{}, + offset{} {} + + sparse_set_iterator(const Container &ref, const difference_type idx) ENTT_NOEXCEPT + : packed{std::addressof(ref)}, + offset{idx} {} + + sparse_set_iterator &operator++() ENTT_NOEXCEPT { + return --offset, *this; + } + + sparse_set_iterator operator++(int) ENTT_NOEXCEPT { + sparse_set_iterator orig = *this; + return ++(*this), orig; + } + + sparse_set_iterator &operator--() ENTT_NOEXCEPT { + return ++offset, *this; + } + + sparse_set_iterator operator--(int) ENTT_NOEXCEPT { + sparse_set_iterator orig = *this; + return operator--(), orig; + } + + sparse_set_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + offset -= value; + return *this; + } + + sparse_set_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + sparse_set_iterator copy = *this; + return (copy += value); + } + + sparse_set_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); + } + + sparse_set_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); + } + + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + return packed->data()[index() - value]; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return packed->data() + index(); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return *operator->(); + } + + [[nodiscard]] difference_type index() const ENTT_NOEXCEPT { + return offset - 1; + } + +private: + const Container *packed; + difference_type offset; +}; + +template +[[nodiscard]] std::ptrdiff_t operator-(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return rhs.index() - lhs.index(); +} + +template +[[nodiscard]] bool operator==(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() == rhs.index(); +} + +template +[[nodiscard]] bool operator!=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +[[nodiscard]] bool operator<(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() > rhs.index(); +} + +template +[[nodiscard]] bool operator>(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() < rhs.index(); +} + +template +[[nodiscard]] bool operator<=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +template +[[nodiscard]] bool operator>=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @brief Sparse set deletion policy. */ +enum class deletion_policy : std::uint8_t { + /*! @brief Swap-and-pop deletion policy. */ + swap_and_pop = 0u, + /*! @brief In-place deletion policy. */ + in_place = 1u +}; + +/** + * @brief Basic 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 is largely used by the registry to offer users the fastest access ever + * to the components. Views and groups in general are almost entirely designed + * around sparse sets. + * + * 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 (see entt_traits for more details). + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +class basic_sparse_set { + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using sparse_container_type = std::vector>; + using packed_container_type = std::vector; + using entity_traits = entt_traits; + + [[nodiscard]] auto sparse_ptr(const Entity entt) const { + const auto pos = static_cast(entity_traits::to_entity(entt)); + const auto page = pos / entity_traits::page_size; + return (page < sparse.size() && sparse[page]) ? (sparse[page] + fast_mod(pos, entity_traits::page_size)) : nullptr; + } + + [[nodiscard]] auto &sparse_ref(const Entity entt) const { + ENTT_ASSERT(sparse_ptr(entt), "Invalid element"); + const auto pos = static_cast(entity_traits::to_entity(entt)); + return sparse[pos / entity_traits::page_size][fast_mod(pos, entity_traits::page_size)]; + } + + [[nodiscard]] auto &assure_at_least(const Entity entt) { + const auto pos = static_cast(entity_traits::to_entity(entt)); + const auto page = pos / entity_traits::page_size; + + if(!(page < sparse.size())) { + sparse.resize(page + 1u, nullptr); + } + + if(!sparse[page]) { + auto page_allocator{packed.get_allocator()}; + sparse[page] = alloc_traits::allocate(page_allocator, entity_traits::page_size); + std::uninitialized_fill(sparse[page], sparse[page] + entity_traits::page_size, null); + } + + auto &elem = sparse[page][fast_mod(pos, entity_traits::page_size)]; + ENTT_ASSERT(entity_traits::to_version(elem) == entity_traits::to_version(tombstone), "Slot not available"); + return elem; + } + + void release_sparse_pages() { + auto page_allocator{packed.get_allocator()}; + + for(auto &&page: sparse) { + if(page != nullptr) { + std::destroy(page, page + entity_traits::page_size); + alloc_traits::deallocate(page_allocator, page, entity_traits::page_size); + page = nullptr; + } + } + } + +private: + virtual const void *get_at(const std::size_t) const { + return nullptr; + } + + virtual void swap_at(const std::size_t, const std::size_t) {} + virtual void move_element(const std::size_t, const std::size_t) {} + +protected: + /*! @brief Random access iterator type. */ + using basic_iterator = internal::sparse_set_iterator; + + /** + * @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 swap_and_pop(basic_iterator first, basic_iterator last) { + for(; first != last; ++first) { + auto &self = sparse_ref(*first); + const auto entt = entity_traits::to_entity(self); + sparse_ref(packed.back()) = entity_traits::combine(entt, entity_traits::to_integral(packed.back())); + packed[static_cast(entt)] = packed.back(); + // unnecessary but it helps to detect nasty bugs + ENTT_ASSERT((packed.back() = tombstone, true), ""); + // lazy self-assignment guard + self = null; + packed.pop_back(); + } + } + + /** + * @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 in_place_pop(basic_iterator first, basic_iterator last) { + for(; first != last; ++first) { + const auto entt = entity_traits::to_entity(std::exchange(sparse_ref(*first), null)); + packed[static_cast(entt)] = std::exchange(free_list, entity_traits::combine(entt, entity_traits::reserved)); + } + } + + /** + * @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(!contains(entt), "Set already contains entity"); + + if(auto &elem = assure_at_least(entt); free_list == null || force_back) { + packed.push_back(entt); + elem = entity_traits::combine(static_cast(packed.size() - 1u), entity_traits::to_integral(entt)); + return begin(); + } else { + const auto pos = static_cast(entity_traits::to_entity(free_list)); + elem = entity_traits::combine(entity_traits::to_integral(free_list), entity_traits::to_integral(entt)); + free_list = std::exchange(packed[pos], entt); + return --(end() - pos); + } + } + +public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Underlying version type. */ + using version_type = typename entity_traits::version_type; + /*! @brief Unsigned integer type. */ + using size_type = typename packed_container_type::size_type; + /*! @brief Pointer type to contained entities. */ + using pointer = typename 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 = std::reverse_iterator; + /*! @brief Constant reverse iterator type. */ + using const_reverse_iterator = 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{type_id(), 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 value 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 &value, deletion_policy pol = deletion_policy::swap_and_pop, const allocator_type &allocator = {}) + : sparse{allocator}, + packed{allocator}, + info{&value}, + free_list{tombstone}, + mode{pol} {} + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_sparse_set(basic_sparse_set &&other) ENTT_NOEXCEPT + : sparse{std::move(other.sparse)}, + packed{std::move(other.packed)}, + info{other.info}, + free_list{std::exchange(other.free_list, tombstone)}, + mode{other.mode} {} + + /** + * @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) ENTT_NOEXCEPT + : sparse{std::move(other.sparse), allocator}, + packed{std::move(other.packed), allocator}, + info{other.info}, + free_list{std::exchange(other.free_list, tombstone)}, + mode{other.mode} { + ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.get_allocator() == other.packed.get_allocator(), "Copying a sparse set is not allowed"); + } + + /*! @brief Default destructor. */ + virtual ~basic_sparse_set() { + release_sparse_pages(); + } + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This sparse set. + */ + basic_sparse_set &operator=(basic_sparse_set &&other) ENTT_NOEXCEPT { + ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.get_allocator() == other.packed.get_allocator(), "Copying a sparse set is not allowed"); + + release_sparse_pages(); + sparse = std::move(other.sparse); + packed = std::move(other.packed); + info = other.info; + free_list = std::exchange(other.free_list, tombstone); + mode = other.mode; + 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) { + using std::swap; + swap(sparse, other.sparse); + swap(packed, other.packed); + swap(info, other.info); + swap(free_list, other.free_list); + swap(mode, other.mode); + } + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_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 ENTT_NOEXCEPT { + return mode; + } + + /** + * @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 ENTT_NOEXCEPT { + return packed.capacity(); + } + + /*! @brief Requests the removal of unused capacity. */ + virtual void 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 the internal packed 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 Extent of the sparse set. + */ + [[nodiscard]] size_type extent() const ENTT_NOEXCEPT { + return sparse.size() * entity_traits::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 ENTT_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 ENTT_NOEXCEPT { + return packed.empty(); + } + + /** + * @brief Direct access to the internal packed array. + * @return A pointer to the internal packed array. + */ + [[nodiscard]] pointer data() const ENTT_NOEXCEPT { + return packed.data(); + } + + /** + * @brief Returns an iterator to the beginning. + * + * The returned iterator points to the first entity of the internal packed + * array. 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]] const_iterator begin() const ENTT_NOEXCEPT { + const auto pos = static_cast(packed.size()); + return iterator{packed, pos}; + } + + /*! @copydoc begin */ + [[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT { + return begin(); + } + + /** + * @brief Returns an iterator to the end. + * + * The returned iterator points to the element following the last entity in + * a sparse set. Attempting to dereference the returned iterator results in + * undefined behavior. + * + * @return An iterator to the element following the last entity of a sparse + * set. + */ + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return iterator{packed, {}}; + } + + /*! @copydoc end */ + [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { + return end(); + } + + /** + * @brief Returns a reverse iterator to the beginning. + * + * The returned iterator points to the first entity of the reversed internal + * packed array. 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]] const_reverse_iterator rbegin() const ENTT_NOEXCEPT { + return std::make_reverse_iterator(end()); + } + + /*! @copydoc rbegin */ + [[nodiscard]] const_reverse_iterator crbegin() const ENTT_NOEXCEPT { + return rbegin(); + } + + /** + * @brief Returns a reverse iterator to the end. + * + * The returned iterator points to the element following the last entity in + * the reversed sparse set. Attempting to dereference the returned iterator + * results in undefined behavior. + * + * @return An iterator to the element following the last entity of the + * reversed sparse set. + */ + [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT { + return std::make_reverse_iterator(begin()); + } + + /*! @copydoc rend */ + [[nodiscard]] const_reverse_iterator crend() const ENTT_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]] iterator find(const entity_type entt) const ENTT_NOEXCEPT { + return contains(entt) ? --(end() - index(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 ENTT_NOEXCEPT { + const auto elem = sparse_ptr(entt); + constexpr auto cap = entity_traits::to_entity(null); + // testing versions permits to avoid accessing the packed array + return elem && (((~cap & entity_traits::to_integral(entt)) ^ entity_traits::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 ENTT_NOEXCEPT { + const auto elem = sparse_ptr(entt); + constexpr auto fallback = entity_traits::to_version(tombstone); + return elem ? entity_traits::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 ENTT_NOEXCEPT { + ENTT_ASSERT(contains(entt), "Set does not contain entity"); + return static_cast(entity_traits::to_entity(sparse_ref(entt))); + } + + /** + * @brief Returns the entity at specified location, with bounds checking. + * @param pos The position for which to return the entity. + * @return The entity at specified location if any, a null entity otherwise. + */ + [[nodiscard]] entity_type at(const size_type pos) const ENTT_NOEXCEPT { + return pos < packed.size() ? packed[pos] : null; + } + + /** + * @brief Returns the entity at specified location, without bounds checking. + * @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 ENTT_NOEXCEPT { + ENTT_ASSERT(pos < packed.size(), "Position is 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. + */ + const void *get(const entity_type entt) const ENTT_NOEXCEPT { + return get_at(index(entt)); + } + + /*! @copydoc get */ + void *get(const entity_type entt) ENTT_NOEXCEPT { + return const_cast(std::as_const(*this).get(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 value Optional opaque value to forward to mixins, if any. + * @return Iterator pointing to the emplaced element in case of success, the + * `end()` iterator otherwise. + */ + iterator emplace(const entity_type entt, const void *value = nullptr) { + return try_emplace(entt, false, value); + } + + /** + * @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. + */ + void bump(const entity_type entt) { + auto &entity = sparse_ref(entt); + entity = entity_traits::combine(entity_traits::to_integral(entity), entity_traits::to_integral(entt)); + packed[static_cast(entity_traits::to_entity(entity))] = entt; + } + + /** + * @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. + * + * @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 Iterator pointing to the first element inserted in case of + * success, the `end()` iterator otherwise. + */ + template + iterator insert(It first, It last) { + for(auto it = first; it != last; ++it) { + try_emplace(*it, true); + } + + return first == last ? end() : find(*first); + } + + /** + * @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 = --(end() - index(entt)); + (mode == deletion_policy::in_place) ? in_place_pop(it, it + 1u) : swap_and_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(std::is_same_v) { + (mode == deletion_policy::in_place) ? in_place_pop(first, last) : swap_and_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{}; + + for(; first != last; ++first) { + count += remove(*first); + } + + return count; + } + + /*! @brief Removes all tombstones from the packed array of a sparse set. */ + void compact() { + size_type from = packed.size(); + for(; from && packed[from - 1u] == tombstone; --from) {} + + for(auto *it = &free_list; *it != null && from; it = std::addressof(packed[entity_traits::to_entity(*it)])) { + if(const size_type to = entity_traits::to_entity(*it); to < from) { + --from; + move_element(from, to); + + using std::swap; + swap(packed[from], packed[to]); + + const auto entity = static_cast(to); + sparse_ref(packed[to]) = entity_traits::combine(entity, entity_traits::to_integral(packed[to])); + *it = entity_traits::combine(static_cast(from), entity_traits::reserved); + for(; from && packed[from - 1u] == tombstone; --from) {} + } + } + + free_list = tombstone; + packed.resize(from); + } + + /** + * @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) { + ENTT_ASSERT(contains(lhs) && contains(rhs), "Set does not contain entities"); + + auto &entt = sparse_ref(lhs); + auto &other = sparse_ref(rhs); + + const auto from = entity_traits::to_entity(entt); + const auto to = entity_traits::to_entity(other); + + // basic no-leak guarantee (with invalid state) if swapping throws + swap_at(static_cast(from), static_cast(to)); + entt = entity_traits::combine(to, entity_traits::to_integral(packed[from])); + other = entity_traits::combine(from, entity_traits::to_integral(packed[to])); + + using std::swap; + swap(packed[from], packed[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(!(length > packed.size()), "Length exceeds the number of elements"); + ENTT_ASSERT(free_list == null, "Partial sorting with tombstones is not supported"); + + algo(packed.rend() - length, packed.rend(), std::move(compare), std::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_at(next, idx); + const auto entity = static_cast(curr); + sparse_ref(entt) = entity_traits::combine(entity, entity_traits::to_integral(packed[curr])); + curr = std::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) { + compact(); + sort_n(packed.size(), std::move(compare), std::move(algo), std::forward(args)...); + } + + /** + * @brief Sort entities according to their order in another sparse set. + * + * Entities that are part of both the sparse sets are ordered internally + * according to the order they have in `other`. All the other entities goes + * to the end of the list and there are no guarantees on their order.
+ * In other terms, this function can be used to impose the same order on two + * sets by using one of them as a master and the other one as a slave. + * + * Iterating the sparse set with a couple of iterators returns elements in + * the expected order after a call to `respect`. See `begin` and `end` for + * more details. + * + * @param other The sparse sets that imposes the order of the entities. + */ + void respect(const basic_sparse_set &other) { + compact(); + + const auto to = other.end(); + auto from = other.begin(); + + for(size_type pos = packed.size() - 1; pos && from != to; ++from) { + if(contains(*from)) { + if(*from != packed[pos]) { + // basic no-leak guarantee (with invalid state) if swapping throws + swap_elements(packed[pos], *from); + } + + --pos; + } + } + } + + /*! @brief Clears a sparse set. */ + void clear() { + if(const auto last = end(); free_list == null) { + in_place_pop(begin(), last); + } else { + for(auto &&entity: *this) { + // tombstone filter on itself + if(const auto it = find(entity); it != last) { + in_place_pop(it, it + 1u); + } + } + } + + free_list = tombstone; + packed.clear(); + } + + /** + * @brief Returned value type, if any. + * @return Returned value type, if any. + */ + const type_info &type() const ENTT_NOEXCEPT { + return *info; + } + + /*! @brief Forwards variables to mixins, if any. */ + virtual void bind(any) ENTT_NOEXCEPT {} + +private: + sparse_container_type sparse; + packed_container_type packed; + const type_info *info; + entity_type free_list; + deletion_policy mode; +}; + +} // namespace entt + +#endif + +// #include "entity/storage.hpp" +#ifndef ENTT_ENTITY_STORAGE_HPP +#define ENTT_ENTITY_STORAGE_HPP + +#include +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/compressed_pair.hpp" + +// #include "../core/iterator.hpp" + +// #include "../core/memory.hpp" + +// #include "../core/type_info.hpp" + +// #include "component.hpp" + +// #include "entity.hpp" + +// #include "fwd.hpp" + +// #include "sigh_storage_mixin.hpp" + +// #include "sparse_set.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +class storage_iterator final { + friend storage_iterator; + + using container_type = std::remove_const_t; + using alloc_traits = std::allocator_traits; + using comp_traits = component_traits; + + using iterator_traits = std::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 = typename iterator_traits::value_type; + using pointer = typename iterator_traits::pointer; + using reference = typename iterator_traits::reference; + using difference_type = typename iterator_traits::difference_type; + using iterator_category = std::random_access_iterator_tag; + + storage_iterator() ENTT_NOEXCEPT = default; + + storage_iterator(Container *ref, difference_type idx) ENTT_NOEXCEPT + : packed{ref}, + offset{idx} {} + + template, typename = std::enable_if_t> + storage_iterator(const storage_iterator> &other) ENTT_NOEXCEPT + : packed{other.packed}, + offset{other.offset} {} + + storage_iterator &operator++() ENTT_NOEXCEPT { + return --offset, *this; + } + + storage_iterator operator++(int) ENTT_NOEXCEPT { + storage_iterator orig = *this; + return ++(*this), orig; + } + + storage_iterator &operator--() ENTT_NOEXCEPT { + return ++offset, *this; + } + + storage_iterator operator--(int) ENTT_NOEXCEPT { + storage_iterator orig = *this; + return operator--(), orig; + } + + storage_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + offset -= value; + return *this; + } + + storage_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + storage_iterator copy = *this; + return (copy += value); + } + + storage_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); + } + + storage_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); + } + + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + const auto pos = index() - value; + return (*packed)[pos / comp_traits::page_size][fast_mod(pos, comp_traits::page_size)]; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + const auto pos = index(); + return (*packed)[pos / comp_traits::page_size] + fast_mod(pos, comp_traits::page_size); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return *operator->(); + } + + [[nodiscard]] difference_type index() const ENTT_NOEXCEPT { + return offset - 1; + } + +private: + Container *packed; + difference_type offset; +}; + +template +[[nodiscard]] std::ptrdiff_t operator-(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return rhs.index() - lhs.index(); +} + +template +[[nodiscard]] bool operator==(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() == rhs.index(); +} + +template +[[nodiscard]] bool operator!=(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +[[nodiscard]] bool operator<(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() > rhs.index(); +} + +template +[[nodiscard]] bool operator>(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() < rhs.index(); +} + +template +[[nodiscard]] bool operator<=(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +template +[[nodiscard]] bool operator>=(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +template +class extended_storage_iterator final { + template + friend class extended_storage_iterator; + +public: + using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval()), std::forward_as_tuple(*std::declval()...))); + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + extended_storage_iterator() = default; + + extended_storage_iterator(It base, Other... other) + : it{base, other...} {} + + template && ...) && (std::is_constructible_v && ...)>> + extended_storage_iterator(const extended_storage_iterator &other) + : it{other.it} {} + + extended_storage_iterator &operator++() ENTT_NOEXCEPT { + return ++std::get(it), (++std::get(it), ...), *this; + } + + extended_storage_iterator operator++(int) ENTT_NOEXCEPT { + extended_storage_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return {*std::get(it), *std::get(it)...}; + } + + template + friend bool operator==(const extended_storage_iterator &, const extended_storage_iterator &) ENTT_NOEXCEPT; + +private: + std::tuple it; +}; + +template +[[nodiscard]] bool operator==(const extended_storage_iterator &lhs, const extended_storage_iterator &rhs) ENTT_NOEXCEPT { + return std::get<0>(lhs.it) == std::get<0>(rhs.it); +} + +template +[[nodiscard]] bool operator!=(const extended_storage_iterator &lhs, const extended_storage_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Basic 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 Entity A valid entity type (see entt_traits for more details). + * @tparam Type Type of objects assigned to the entities. + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +class basic_storage: public basic_sparse_set::template rebind_alloc> { + static_assert(std::is_move_constructible_v && std::is_move_assignable_v, "The type must be at least move constructible/assignable"); + + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using underlying_type = basic_sparse_set>; + using container_type = std::vector>; + using comp_traits = component_traits; + + [[nodiscard]] auto &element_at(const std::size_t pos) const { + return packed.first()[pos / comp_traits::page_size][fast_mod(pos, comp_traits::page_size)]; + } + + auto assure_at_least(const std::size_t pos) { + auto &&container = packed.first(); + const auto idx = pos / comp_traits::page_size; + + if(!(idx < container.size())) { + auto curr = container.size(); + container.resize(idx + 1u, nullptr); + + ENTT_TRY { + for(const auto last = container.size(); curr < last; ++curr) { + container[curr] = alloc_traits::allocate(packed.second(), comp_traits::page_size); + } + } + ENTT_CATCH { + container.resize(curr); + ENTT_THROW; + } + } + + return container[idx] + fast_mod(pos, comp_traits::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 = assure_at_least(static_cast(it.index())); + entt::uninitialized_construct_using_allocator(to_address(elem), packed.second(), std::forward(args)...); + } + ENTT_CATCH { + if constexpr(comp_traits::in_place_delete) { + base_type::in_place_pop(it, it + 1u); + } else { + base_type::swap_and_pop(it, it + 1u); + } + + ENTT_THROW; + } + + return it; + } + + void shrink_to_size(const std::size_t sz) { + for(auto pos = sz, length = base_type::size(); pos < length; ++pos) { + if constexpr(comp_traits::in_place_delete) { + if(base_type::at(pos) != tombstone) { + std::destroy_at(std::addressof(element_at(pos))); + } + } else { + std::destroy_at(std::addressof(element_at(pos))); + } + } + + auto &&container = packed.first(); + auto page_allocator{packed.second()}; + const auto from = (sz + comp_traits::page_size - 1u) / comp_traits::page_size; + + for(auto pos = from, last = container.size(); pos < last; ++pos) { + alloc_traits::deallocate(page_allocator, container[pos], comp_traits::page_size); + } + + container.resize(from); + } + +private: + const void *get_at(const std::size_t pos) const final { + return std::addressof(element_at(pos)); + } + + void swap_at(const std::size_t lhs, const std::size_t rhs) final { + using std::swap; + swap(element_at(lhs), element_at(rhs)); + } + + void move_element(const std::size_t from, const std::size_t to) final { + auto &elem = element_at(from); + entt::uninitialized_construct_using_allocator(to_address(assure_at_least(to)), packed.second(), std::move(elem)); + std::destroy_at(std::addressof(elem)); + } + +protected: + /** + * @brief Erases elements from a storage. + * @param first An iterator to the first element to erase. + * @param last An iterator past the last element to erase. + */ + void swap_and_pop(typename underlying_type::basic_iterator first, typename underlying_type::basic_iterator last) override { + for(; first != last; ++first) { + // cannot use first::index() because it would break with cross iterators + const auto pos = base_type::index(*first); + auto &elem = element_at(base_type::size() - 1u); + // destroying on exit allows reentrant destructors + [[maybe_unused]] auto unused = std::exchange(element_at(pos), std::move(elem)); + std::destroy_at(std::addressof(elem)); + base_type::swap_and_pop(first, first + 1u); + } + } + + /** + * @brief Erases elements from a storage. + * @param first An iterator to the first element to erase. + * @param last An iterator past the last element to erase. + */ + void in_place_pop(typename underlying_type::basic_iterator first, typename underlying_type::basic_iterator last) override { + for(; first != last; ++first) { + // cannot use first::index() because it would break with cross iterators + const auto pos = base_type::index(*first); + base_type::in_place_pop(first, first + 1u); + std::destroy_at(std::addressof(element_at(pos))); + } + } + + /** + * @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. + */ + typename underlying_type::basic_iterator try_emplace([[maybe_unused]] const Entity entt, const bool force_back, const void *value) override { + if(value) { + if constexpr(std::is_copy_constructible_v) { + return emplace_element(entt, force_back, *static_cast(value)); + } else { + return base_type::end(); + } + } else { + if constexpr(std::is_default_constructible_v) { + return emplace_element(entt, force_back); + } else { + return base_type::end(); + } + } + } + +public: + /*! @brief Base type. */ + using base_type = underlying_type; + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Type of the objects assigned to entities. */ + using value_type = Type; + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Pointer type to contained elements. */ + using pointer = typename container_type::pointer; + /*! @brief Constant pointer type to contained elements. */ + using const_pointer = typename 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 = std::reverse_iterator; + /*! @brief Constant reverse iterator type. */ + using const_reverse_iterator = std::reverse_iterator; + /*! @brief Extended iterable storage proxy. */ + using iterable = iterable_adaptor>; + /*! @brief Constant extended iterable storage proxy. */ + using const_iterable = iterable_adaptor>; + + /*! @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(), deletion_policy{comp_traits::in_place_delete}, allocator}, + packed{container_type{allocator}, allocator} {} + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_storage(basic_storage &&other) ENTT_NOEXCEPT + : base_type{std::move(other)}, + packed{std::move(other.packed)} {} + + /** + * @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) ENTT_NOEXCEPT + : base_type{std::move(other), allocator}, + packed{container_type{std::move(other.packed.first()), allocator}, allocator} { + ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.second() == other.packed.second(), "Copying a storage is not allowed"); + } + + /*! @brief Default destructor. */ + ~basic_storage() override { + shrink_to_size(0u); + } + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This storage. + */ + basic_storage &operator=(basic_storage &&other) ENTT_NOEXCEPT { + ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.second() == other.packed.second(), "Copying a storage is not allowed"); + + shrink_to_size(0u); + base_type::operator=(std::move(other)); + packed.first() = std::move(other.packed.first()); + propagate_on_container_move_assignment(packed.second(), other.packed.second()); + 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) { + using std::swap; + underlying_type::swap(other); + propagate_on_container_swap(packed.second(), other.packed.second()); + swap(packed.first(), other.packed.first()); + } + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return allocator_type{packed.second()}; + } + + /** + * @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 ENTT_NOEXCEPT override { + return packed.first().size() * comp_traits::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 ENTT_NOEXCEPT { + return packed.first().data(); + } + + /*! @copydoc raw */ + [[nodiscard]] pointer raw() ENTT_NOEXCEPT { + return packed.first().data(); + } + + /** + * @brief Returns an iterator to the beginning. + * + * The returned iterator points to the first instance of the internal array. + * 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 ENTT_NOEXCEPT { + const auto pos = static_cast(base_type::size()); + return const_iterator{&packed.first(), pos}; + } + + /*! @copydoc cbegin */ + [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT { + return cbegin(); + } + + /*! @copydoc begin */ + [[nodiscard]] iterator begin() ENTT_NOEXCEPT { + const auto pos = static_cast(base_type::size()); + return iterator{&packed.first(), pos}; + } + + /** + * @brief Returns an iterator to the end. + * + * The returned iterator points to the element following the last instance + * of the internal array. Attempting to dereference the returned iterator + * results in undefined behavior. + * + * @return An iterator to the element following the last instance of the + * internal array. + */ + [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { + return const_iterator{&packed.first(), {}}; + } + + /*! @copydoc cend */ + [[nodiscard]] const_iterator end() const ENTT_NOEXCEPT { + return cend(); + } + + /*! @copydoc end */ + [[nodiscard]] iterator end() ENTT_NOEXCEPT { + return iterator{&packed.first(), {}}; + } + + /** + * @brief Returns a reverse iterator to the beginning. + * + * The returned iterator points to the first instance of the reversed + * internal array. 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 ENTT_NOEXCEPT { + return std::make_reverse_iterator(cend()); + } + + /*! @copydoc crbegin */ + [[nodiscard]] const_reverse_iterator rbegin() const ENTT_NOEXCEPT { + return crbegin(); + } + + /*! @copydoc rbegin */ + [[nodiscard]] reverse_iterator rbegin() ENTT_NOEXCEPT { + return std::make_reverse_iterator(end()); + } + + /** + * @brief Returns a reverse iterator to the end. + * + * The returned iterator points to the element following the last instance + * of the reversed internal array. Attempting to dereference the returned + * iterator results in undefined behavior. + * + * @return An iterator to the element following the last instance of the + * reversed internal array. + */ + [[nodiscard]] const_reverse_iterator crend() const ENTT_NOEXCEPT { + return std::make_reverse_iterator(cbegin()); + } + + /*! @copydoc crend */ + [[nodiscard]] const_reverse_iterator rend() const ENTT_NOEXCEPT { + return crend(); + } + + /*! @copydoc rend */ + [[nodiscard]] reverse_iterator rend() ENTT_NOEXCEPT { + return std::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 ENTT_NOEXCEPT { + return element_at(base_type::index(entt)); + } + + /*! @copydoc get */ + [[nodiscard]] value_type &get(const entity_type entt) ENTT_NOEXCEPT { + return const_cast(std::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]] std::tuple get_as_tuple(const entity_type entt) const ENTT_NOEXCEPT { + return std::forward_as_tuple(get(entt)); + } + + /*! @copydoc get_as_tuple */ + [[nodiscard]] std::tuple get_as_tuple(const entity_type entt) ENTT_NOEXCEPT { + return std::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(std::is_aggregate_v) { + const auto it = emplace_element(entt, false, Type{std::forward(args)...}); + return element_at(static_cast(it.index())); + } else { + const auto it = emplace_element(entt, false, std::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); + (std::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. + * + * @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 value An instance of the object to construct. + */ + template + void insert(It first, It last, const value_type &value = {}) { + for(; first != last; ++first) { + emplace_element(*first, true, value); + } + } + + /** + * @brief Assigns one or more entities to a storage and constructs their + * objects from a given range. + * + * @sa construct + * + * @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 objects. + */ + template::value_type, value_type>>> + void insert(EIt first, EIt last, CIt from) { + for(; first != last; ++first, ++from) { + emplace_element(*first, true, *from); + } + } + + /** + * @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 component. + * + * @return An iterable object to use to _visit_ the storage. + */ + [[nodiscard]] iterable each() ENTT_NOEXCEPT { + return {internal::extended_storage_iterator{base_type::begin(), begin()}, internal::extended_storage_iterator{base_type::end(), end()}}; + } + + /*! @copydoc each */ + [[nodiscard]] const_iterable each() const ENTT_NOEXCEPT { + return {internal::extended_storage_iterator{base_type::cbegin(), cbegin()}, internal::extended_storage_iterator{base_type::cend(), cend()}}; + } + +private: + compressed_pair packed; +}; + +/*! @copydoc basic_storage */ +template +class basic_storage>> + : public basic_sparse_set::template rebind_alloc> { + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using underlying_type = basic_sparse_set>; + using comp_traits = component_traits; + +public: + /*! @brief Base type. */ + using base_type = underlying_type; + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Type of the objects assigned to entities. */ + using value_type = Type; + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Extended iterable storage proxy. */ + using iterable = iterable_adaptor>; + /*! @brief Constant extended iterable storage proxy. */ + using const_iterable = iterable_adaptor>; + + /*! @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(), deletion_policy{comp_traits::in_place_delete}, allocator} {} + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_storage(basic_storage &&other) ENTT_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) ENTT_NOEXCEPT + : base_type{std::move(other), allocator} {} + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This storage. + */ + basic_storage &operator=(basic_storage &&other) ENTT_NOEXCEPT = default; + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_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 ENTT_NOEXCEPT { + ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity"); + } + + /** + * @brief Returns an empty tuple. + * + * @warning + * Attempting to use an entity that doesn't belong to the storage results in + * undefined behavior. + * + * @param entt A valid identifier. + * @return Returns an empty tuple. + */ + [[nodiscard]] std::tuple<> get_as_tuple([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { + ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity"); + return std::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. + * + * @tparam Args Types of arguments to use to construct the object. + * @param entt A valid identifier. + */ + template + void emplace(const entity_type entt, Args &&...) { + 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), "Storage does not contain entity"); + (std::forward(func)(), ...); + } + + /** + * @brief Assigns entities to a storage. + * @tparam It Type of input iterator. + * @tparam Args Types of optional arguments. + * @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 insert(It first, It last, Args &&...) { + 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() ENTT_NOEXCEPT { + return {internal::extended_storage_iterator{base_type::begin()}, internal::extended_storage_iterator{base_type::end()}}; + } + + /*! @copydoc each */ + [[nodiscard]] const_iterable each() const ENTT_NOEXCEPT { + return {internal::extended_storage_iterator{base_type::cbegin()}, internal::extended_storage_iterator{base_type::cend()}}; + } +}; + +/** + * @brief Provides a common way to access certain properties of storage types. + * @tparam Entity A valid entity type (see entt_traits for more details). + * @tparam Type Type of objects managed by the storage class. + */ +template +struct storage_traits { + /*! @brief Resulting type after component-to-storage conversion. */ + using storage_type = sigh_storage_mixin>; +}; + +} // namespace entt + +#endif + +// #include "entity/utility.hpp" +#ifndef ENTT_ENTITY_UTILITY_HPP +#define ENTT_ENTITY_UTILITY_HPP + +// #include "../core/type_traits.hpp" + + +namespace entt { + +/** + * @brief Alias for exclusion lists. + * @tparam Type List of types. + */ +template +struct exclude_t: type_list {}; + +/** + * @brief Variable template for exclusion lists. + * @tparam Type List of types. + */ +template +inline constexpr exclude_t exclude{}; + +/** + * @brief Alias for lists of observed components. + * @tparam Type List of types. + */ +template +struct get_t: type_list {}; + +/** + * @brief Variable template for lists of observed components. + * @tparam Type List of types. + */ +template +inline constexpr get_t get{}; + +/** + * @brief Alias for lists of owned components. + * @tparam Type List of types. + */ +template +struct owned_t: type_list {}; + +/** + * @brief Variable template for lists of owned components. + * @tparam Type List of types. + */ +template +inline constexpr owned_t owned{}; + +} // namespace entt + +#endif + +// #include "entity/view.hpp" +#ifndef ENTT_ENTITY_VIEW_HPP +#define ENTT_ENTITY_VIEW_HPP + +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/iterator.hpp" + +// #include "../core/type_traits.hpp" + +// #include "component.hpp" + +// #include "entity.hpp" + +// #include "fwd.hpp" + +// #include "sparse_set.hpp" + +// #include "storage.hpp" + +// #include "utility.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +class view_iterator final { + using iterator_type = typename Type::const_iterator; + + [[nodiscard]] bool valid() const ENTT_NOEXCEPT { + return ((Component != 0u) || (*it != tombstone)) + && std::apply([entt = *it](const auto *...curr) { return (curr->contains(entt) && ...); }, pools) + && std::apply([entt = *it](const auto *...curr) { return (!curr->contains(entt) && ...); }, filter); + } + +public: + using value_type = typename iterator_type::value_type; + using pointer = typename iterator_type::pointer; + using reference = typename iterator_type::reference; + using difference_type = typename iterator_type::difference_type; + using iterator_category = std::forward_iterator_tag; + + view_iterator() ENTT_NOEXCEPT = default; + + view_iterator(iterator_type curr, iterator_type to, std::array all_of, std::array none_of) ENTT_NOEXCEPT + : it{curr}, + last{to}, + pools{all_of}, + filter{none_of} { + if(it != last && !valid()) { + ++(*this); + } + } + + view_iterator &operator++() ENTT_NOEXCEPT { + while(++it != last && !valid()) {} + return *this; + } + + view_iterator operator++(int) ENTT_NOEXCEPT { + view_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return &*it; + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return *operator->(); + } + + template + friend bool operator==(const view_iterator &, const view_iterator &) ENTT_NOEXCEPT; + +private: + iterator_type it; + iterator_type last; + std::array pools; + std::array filter; +}; + +template +[[nodiscard]] bool operator==(const view_iterator &lhs, const view_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] bool operator!=(const view_iterator &lhs, const view_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +struct extended_view_iterator final { + using difference_type = std::ptrdiff_t; + using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval()), std::declval().get_as_tuple({})...)); + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + extended_view_iterator() = default; + + extended_view_iterator(It from, std::tuple storage) + : it{from}, + pools{storage} {} + + extended_view_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } + + extended_view_iterator operator++(int) ENTT_NOEXCEPT { + extended_view_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return std::apply([entt = *it](auto *...curr) { return std::tuple_cat(std::make_tuple(entt), curr->get_as_tuple(entt)...); }, pools); + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + template + friend bool operator==(const extended_view_iterator &, const extended_view_iterator &) ENTT_NOEXCEPT; + +private: + It it; + std::tuple pools; +}; + +template +[[nodiscard]] bool operator==(const extended_view_iterator &lhs, const extended_view_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] bool operator!=(const extended_view_iterator &lhs, const extended_view_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @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. + */ +template +class basic_view; + +/** + * @brief Multi component view. + * + * Multi component views iterate over those entities that have at least all the + * given components in their bags. During initialization, a multi component view + * looks at the number of entities available for each component and uses the + * smallest set in order to get a performance boost when iterate. + * + * @b Important + * + * Iterators aren't invalidated if: + * + * * New instances of the given components are created and assigned to entities. + * * The entity currently pointed is modified (as an example, if one of the + * given components is removed from the entity to which the iterator points). + * * The entity currently pointed is destroyed. + * + * In all other cases, modifying the pools iterated by the view in any way + * invalidates all the iterators and using them results in undefined behavior. + * + * @tparam Entity A valid entity type (see entt_traits for more details). + * @tparam Component Types of components iterated by the view. + * @tparam Exclude Types of components used to filter the view. + */ +template +class basic_view, exclude_t> { + template + friend class basic_view; + + template + using storage_type = constness_as_t>::storage_type, Comp>; + + template + [[nodiscard]] auto pools_to_array(std::index_sequence) const ENTT_NOEXCEPT { + std::size_t pos{}; + std::array other{}; + (static_cast(std::get(pools) == view ? void() : void(other[pos++] = std::get(pools))), ...); + return other; + } + + template + [[nodiscard]] auto dispatch_get(const std::tuple &curr) const { + if constexpr(Comp == Other) { + return std::forward_as_tuple(std::get(curr)...); + } else { + return std::get(pools)->get_as_tuple(std::get<0>(curr)); + } + } + + template + void each(Func func, std::index_sequence) const { + for(const auto curr: std::get(pools)->each()) { + const auto entt = std::get<0>(curr); + + if(((sizeof...(Component) != 1u) || (entt != tombstone)) + && ((Comp == Index || std::get(pools)->contains(entt)) && ...) + && std::apply([entt](const auto *...cpool) { return (!cpool->contains(entt) && ...); }, filter)) { + if constexpr(is_applicable_v{}, std::declval().get({})))>) { + std::apply(func, std::tuple_cat(std::make_tuple(entt), dispatch_get(curr)...)); + } else { + std::apply(func, std::tuple_cat(dispatch_get(curr)...)); + } + } + } + } + + template + void pick_and_each(Func func, std::index_sequence seq) const { + ((std::get(pools) == view ? each(std::move(func), seq) : void()), ...); + } + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using base_type = std::common_type_t::base_type...>; + /*! @brief Bidirectional iterator type. */ + using iterator = internal::view_iterator; + /*! @brief Iterable view type. */ + using iterable = iterable_adaptor...>>; + + /*! @brief Default constructor to use to create empty, invalid views. */ + basic_view() ENTT_NOEXCEPT + : pools{}, + filter{}, + view{} {} + + /** + * @brief Constructs a multi-type view from a set of storage classes. + * @param component The storage for the types to iterate. + * @param epool The storage for the types used to filter the view. + */ + basic_view(storage_type &...component, const storage_type &...epool) ENTT_NOEXCEPT + : pools{&component...}, + filter{&epool...}, + view{(std::min)({&static_cast(component)...}, [](auto *lhs, auto *rhs) { return lhs->size() < rhs->size(); })} {} + + /** + * @brief Creates a new view driven by a given component in its iterations. + * @tparam Comp Type of component used to drive the iteration. + * @return A new view driven by the given component in its iterations. + */ + template + [[nodiscard]] basic_view use() const ENTT_NOEXCEPT { + basic_view other{*this}; + other.view = std::get *>(pools); + return other; + } + + /** + * @brief Creates a new view driven by a given component in its iterations. + * @tparam Comp Index of the component used to drive the iteration. + * @return A new view driven by the given component in its iterations. + */ + template + [[nodiscard]] basic_view use() const ENTT_NOEXCEPT { + basic_view other{*this}; + other.view = std::get(pools); + return other; + } + + /** + * @brief Returns the leading storage of a view. + * @return The leading storage of the view. + */ + const base_type &handle() const ENTT_NOEXCEPT { + return *view; + } + + /** + * @brief Returns the storage for a given component type. + * @tparam Comp Type of component of which to return the storage. + * @return The storage for the given component type. + */ + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get *>(pools); + } + + /** + * @brief Returns the storage for a given component type. + * @tparam Comp Index of component of which to return the storage. + * @return The storage for the given component type. + */ + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get(pools); + } + + /** + * @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 ENTT_NOEXCEPT { + return view->size(); + } + + /** + * @brief Returns an iterator to the first entity of the view. + * + * The returned iterator points 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 ENTT_NOEXCEPT { + return iterator{view->begin(), view->end(), pools_to_array(std::index_sequence_for{}), filter}; + } + + /** + * @brief Returns an iterator that is past the last entity of the view. + * + * The returned iterator points to the entity following the last entity of + * the view. Attempting to dereference the returned iterator results in + * undefined behavior. + * + * @return An iterator to the entity following the last entity of the view. + */ + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return iterator{view->end(), view->end(), pools_to_array(std::index_sequence_for{}), filter}; + } + + /** + * @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 ENTT_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 ENTT_NOEXCEPT { + auto it = view->rbegin(); + for(const auto last = view->rend(); it != last && !contains(*it); ++it) {} + return it == view->rend() ? null : *it; + } + + /** + * @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 ENTT_NOEXCEPT { + return contains(entt) ? iterator{view->find(entt), view->end(), pools_to_array(std::index_sequence_for{}), filter} : end(); + } + + /** + * @brief Returns the components assigned to the given entity. + * @param entt A valid identifier. + * @return The components assigned to the given entity. + */ + [[nodiscard]] decltype(auto) operator[](const entity_type entt) const { + return get(entt); + } + + /** + * @brief Checks if a view is properly initialized. + * @return True if the view is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return view != 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 ENTT_NOEXCEPT { + return std::apply([entt](const auto *...curr) { return (curr->contains(entt) && ...); }, pools) + && std::apply([entt](const auto *...curr) { return (!curr->contains(entt) && ...); }, filter); + } + + /** + * @brief Returns the components assigned to the given entity. + * + * @warning + * Attempting to use an entity that doesn't belong to the view results in + * undefined behavior. + * + * @tparam Comp Types of components to get. + * @param entt A valid identifier. + * @return The components assigned to the entity. + */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + ENTT_ASSERT(contains(entt), "View does not contain entity"); + + if constexpr(sizeof...(Comp) == 0) { + return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, pools); + } else if constexpr(sizeof...(Comp) == 1) { + return (std::get *>(pools)->get(entt), ...); + } else { + return std::tuple_cat(std::get *>(pools)->get_as_tuple(entt)...); + } + } + + /** + * @brief Returns the components assigned to the given entity. + * + * @warning + * Attempting to use an entity that doesn't belong to the view results in + * undefined behavior. + * + * @tparam First Index of a component to get. + * @tparam Other Indexes of other components to get. + * @param entt A valid identifier. + * @return The components assigned to the entity. + */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + ENTT_ASSERT(contains(entt), "View does not contain entity"); + + if constexpr(sizeof...(Other) == 0) { + return std::get(pools)->get(entt); + } else { + return std::tuple_cat(std::get(pools)->get_as_tuple(entt), std::get(pools)->get_as_tuple(entt)...); + } + } + + /** + * @brief Iterates entities and components 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 components. The + * _constness_ of the components 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 + * + * @tparam Func Type of the function object to invoke. + * @param func A valid function object. + */ + template + void each(Func func) const { + pick_and_each(std::move(func), std::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 components. The _constness_ of the + * components is as requested. + * + * @return An iterable object to use to _visit_ the view. + */ + [[nodiscard]] iterable each() const ENTT_NOEXCEPT { + return {internal::extended_view_iterator{begin(), pools}, internal::extended_view_iterator{end(), pools}}; + } + + /** + * @brief Combines two views in a _more specific_ one (friend function). + * @tparam Get Component list of the view to combine with. + * @tparam Excl Filter list of the view to combine with. + * @param other The view to combine with. + * @return A more specific view. + */ + template + [[nodiscard]] auto operator|(const basic_view, exclude_t> &other) const ENTT_NOEXCEPT { + using view_type = basic_view, exclude_t>; + return std::make_from_tuple(std::tuple_cat( + std::apply([](auto *...curr) { return std::forward_as_tuple(*curr...); }, pools), + std::apply([](auto *...curr) { return std::forward_as_tuple(*curr...); }, other.pools), + std::apply([](const auto *...curr) { return std::forward_as_tuple(static_cast &>(*curr)...); }, filter), + std::apply([](const auto *...curr) { return std::forward_as_tuple(static_cast &>(*curr)...); }, other.filter))); + } + +private: + std::tuple *...> pools; + std::array filter; + const base_type *view; +}; + +/** + * @brief Single component view specialization. + * + * Single component views are specialized in order to get a boost in terms of + * performance. This kind of views can access the underlying data structure + * directly and avoid superfluous checks. + * + * @b Important + * + * Iterators aren't invalidated if: + * + * * New instances of the given component are created and assigned to entities. + * * The entity currently pointed is modified (as an example, the given + * component is removed from the entity to which the iterator points). + * * The entity currently pointed is destroyed. + * + * In all other cases, modifying the pool iterated by the view in any way + * invalidates all the iterators and using them results in undefined behavior. + * + * @tparam Entity A valid entity type (see entt_traits for more details). + * @tparam Component Type of component iterated by the view. + */ +template +class basic_view, exclude_t<>, std::void_t>::in_place_delete>>> { + template + friend class basic_view; + + using storage_type = constness_as_t>::storage_type, Component>; + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using base_type = typename storage_type::base_type; + /*! @brief Random access iterator type. */ + using iterator = typename base_type::iterator; + /*! @brief Reversed iterator type. */ + using reverse_iterator = typename base_type::reverse_iterator; + /*! @brief Iterable view type. */ + using iterable = decltype(std::declval().each()); + + /*! @brief Default constructor to use to create empty, invalid views. */ + basic_view() ENTT_NOEXCEPT + : pools{}, + filter{}, + view{} {} + + /** + * @brief Constructs a single-type view from a storage class. + * @param ref The storage for the type to iterate. + */ + basic_view(storage_type &ref) ENTT_NOEXCEPT + : pools{&ref}, + filter{}, + view{&ref} {} + + /** + * @brief Returns the leading storage of a view. + * @return The leading storage of the view. + */ + const base_type &handle() const ENTT_NOEXCEPT { + return *view; + } + + /** + * @brief Returns the storage for a given component type. + * @tparam Comp Type of component of which to return the storage. + * @return The storage for the given component type. + */ + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + static_assert(std::is_same_v, "Invalid component type"); + return *std::get<0>(pools); + } + + /** + * @brief Returns the storage for a given component type. + * @tparam Comp Index of component of which to return the storage. + * @return The storage for the given component type. + */ + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get(pools); + } + + /** + * @brief Returns the number of entities that have the given component. + * @return Number of entities that have the given component. + */ + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return view->size(); + } + + /** + * @brief Checks whether a view is empty. + * @return True if the view is empty, false otherwise. + */ + [[nodiscard]] bool empty() const ENTT_NOEXCEPT { + return view->empty(); + } + + /** + * @brief Returns an iterator to the first entity of the view. + * + * The returned iterator points 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 ENTT_NOEXCEPT { + return view->begin(); + } + + /** + * @brief Returns an iterator that is past the last entity of the view. + * + * The returned iterator points to the entity following the last entity of + * the view. Attempting to dereference the returned iterator results in + * undefined behavior. + * + * @return An iterator to the entity following the last entity of the view. + */ + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return view->end(); + } + + /** + * @brief Returns an iterator to the first entity of the reversed view. + * + * The returned iterator points 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 ENTT_NOEXCEPT { + return view->rbegin(); + } + + /** + * @brief Returns an iterator that is past the last entity of the reversed + * view. + * + * The returned iterator points to the entity following the last entity of + * the reversed view. Attempting to dereference the returned iterator + * results in undefined behavior. + * + * @return An iterator to the entity following the last entity of the + * reversed view. + */ + [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT { + return view->rend(); + } + + /** + * @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 ENTT_NOEXCEPT { + return empty() ? null : *begin(); + } + + /** + * @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 ENTT_NOEXCEPT { + return empty() ? null : *rbegin(); + } + + /** + * @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 ENTT_NOEXCEPT { + return contains(entt) ? view->find(entt) : end(); + } + + /** + * @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()[pos]; + } + + /** + * @brief Returns the component assigned to the given entity. + * @param entt A valid identifier. + * @return The component assigned to the given entity. + */ + [[nodiscard]] decltype(auto) operator[](const entity_type entt) const { + return get(entt); + } + + /** + * @brief Checks if a view is properly initialized. + * @return True if the view is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return view != 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 ENTT_NOEXCEPT { + return view->contains(entt); + } + + /** + * @brief Returns the component assigned to the given entity. + * + * @warning + * Attempting to use an entity that doesn't belong to the view results in + * undefined behavior. + * + * @tparam Comp Type or index of the component to get. + * @param entt A valid identifier. + * @return The component assigned to the entity. + */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + ENTT_ASSERT(contains(entt), "View does not contain entity"); + + if constexpr(sizeof...(Comp) == 0) { + return std::get<0>(pools)->get_as_tuple(entt); + } else { + static_assert(std::is_same_v, "Invalid component type"); + return std::get<0>(pools)->get(entt); + } + } + + /*! @copydoc get */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + ENTT_ASSERT(contains(entt), "View does not contain entity"); + return std::get<0>(pools)->get(entt); + } + + /** + * @brief Iterates entities and components 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 reference to the component if it's a non-empty one. + * The _constness_ of the component is as requested.
+ * The signature of the function must be equivalent to one of the following + * forms: + * + * @code{.cpp} + * void(const entity_type, Component &); + * void(Component &); + * @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) { + for(const auto pack: each()) { + std::apply(func, pack); + } + } else if constexpr(std::is_invocable_v) { + for(auto &&component: *std::get<0>(pools)) { + func(component); + } + } else if constexpr(std::is_invocable_v) { + for(auto entity: *view) { + func(entity); + } + } else { + for(size_type pos{}, last = size(); pos < last; ++pos) { + func(); + } + } + } + + /** + * @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 component if it's a non-empty one. The _constness_ of + * the component is as requested. + * + * @return An iterable object to use to _visit_ the view. + */ + [[nodiscard]] iterable each() const ENTT_NOEXCEPT { + return std::get<0>(pools)->each(); + } + + /** + * @brief Combines two views in a _more specific_ one (friend function). + * @tparam Get Component list of the view to combine with. + * @tparam Excl Filter list of the view to combine with. + * @param other The view to combine with. + * @return A more specific view. + */ + template + [[nodiscard]] auto operator|(const basic_view, exclude_t> &other) const ENTT_NOEXCEPT { + using view_type = basic_view, exclude_t>; + return std::make_from_tuple(std::tuple_cat( + std::forward_as_tuple(*std::get<0>(pools)), + std::apply([](auto *...curr) { return std::forward_as_tuple(*curr...); }, other.pools), + std::apply([](const auto *...curr) { return std::forward_as_tuple(static_cast &>(*curr)...); }, other.filter))); + } + +private: + std::tuple pools; + std::array filter; + const base_type *view; +}; + +/** + * @brief Deduction guide. + * @tparam Storage Type of storage classes used to create the view. + * @param storage The storage for the types to iterate. + */ +template +basic_view(Storage &...storage) -> basic_view, get_t...>, exclude_t<>>; + +} // namespace entt + +#endif + +// #include "locator/locator.hpp" +#ifndef ENTT_LOCATOR_LOCATOR_HPP +#define ENTT_LOCATOR_LOCATOR_HPP + +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + + +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 +struct locator final { + /*! @brief Service type. */ + using type = Service; + + /*! @brief Default constructor, deleted on purpose. */ + locator() = delete; + /*! @brief Default destructor, deleted on purpose. */ + ~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() ENTT_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() ENTT_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 Impl Fallback service type. + * @param args Parameters to use to construct the fallback service. + * @return A reference to a valid service. + */ + template + [[nodiscard]] static Service &value_or(Args &&...args) { + return service ? *service : emplace(std::forward(args)...); + } + + /** + * @brief Sets or replaces a service. + * @tparam Impl 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 + static Service &emplace(Args &&...args) { + service = std::make_shared(std::forward(args)...); + return *service; + } + + /** + * @brief Sets or replaces a service using a given allocator. + * @tparam Impl Service type. + * @tparam Allocator Type of allocator used to manage memory and elements. + * @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 + static Service &allocate_emplace(Allocator alloc, Args &&...args) { + service = std::allocate_shared(alloc, std::forward(args)...); + return *service; + } + + /*! @brief Resets a service. */ + static void reset() ENTT_NOEXCEPT { + service.reset(); + } + +private: + // std::shared_ptr because of its type erased allocator which is pretty useful here + inline static std::shared_ptr service = nullptr; +}; + +} // namespace entt + +#endif + +// #include "meta/adl_pointer.hpp" +#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 + +// #include "meta/container.hpp" +#ifndef ENTT_META_CONTAINER_HPP +#define ENTT_META_CONTAINER_HPP + +#include +#include +#include +#include +#include +#include +#include +// #include "../container/dense_map.hpp" +#ifndef ENTT_CONTAINER_DENSE_MAP_HPP +#define ENTT_CONTAINER_DENSE_MAP_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "../core/compressed_pair.hpp" +#ifndef ENTT_CORE_COMPRESSED_PAIR_HPP +#define ENTT_CORE_COMPRESSED_PAIR_HPP + +#include +#include +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP + +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "fwd.hpp" +#ifndef ENTT_CORE_FWD_HPP +#define ENTT_CORE_FWD_HPP + +#include +#include +// #include "../config/config.h" + + +namespace entt { + +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<>; + +} // namespace entt + +#endif + + +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 TURN_OFF_DOXYGEN */ 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 Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; + +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; + +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; + +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; + +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::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 = std::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 Type 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 Type 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 = Type; +}; + +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; + +/** + * @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 +constexpr 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 = typename 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 = typename type_list_cat::type; + +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; + +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; +}; + +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A type list. + */ +template +using type_list_unique_t = typename type_list_unique::type; + +/** + * @brief Provides the member constant `value` 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>: std::disjunction...> {}; + +/** + * @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 = typename type_list_diff::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 value. */ + static constexpr auto value = Value; +}; + +/** + * @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 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 +constexpr 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 = typename 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 = typename value_list_cat::type; + +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::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>: std::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>: std::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 std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::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>: std::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` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; + +/*! @copydoc is_complete */ +template +struct is_complete>: std::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` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_iterator_category: std::false_type {}; + +template +struct has_iterator_category::iterator_category>>: std::true_type {}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : 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` 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 + : std::conjunction, std::negation>> {}; + +/** + * @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` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; + +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; + +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_tuple_size_value: std::false_type {}; + +template +struct has_tuple_size_value::value)>>: std::true_type {}; + +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} + +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} + +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; + } +} + +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); + } +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; + +/** + * @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 = std::remove_const_t; +}; + +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; + +/** + * @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 = typename constness_as::type; + +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); + + 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 = std::remove_pointer_t()))>; +}; + +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct compressed_pair_element { + using reference = Type &; + using const_reference = const Type &; + + template>> + compressed_pair_element() + : value{} {} + + template>, compressed_pair_element>>> + compressed_pair_element(Args &&args) + : value{std::forward(args)} {} + + template + compressed_pair_element(std::tuple args, std::index_sequence) + : value{std::forward(std::get(args))...} {} + + [[nodiscard]] reference get() ENTT_NOEXCEPT { + return value; + } + + [[nodiscard]] const_reference get() const ENTT_NOEXCEPT { + return value; + } + +private: + Type value; +}; + +template +struct compressed_pair_element>>: Type { + using reference = Type &; + using const_reference = const Type &; + using base_type = Type; + + template>> + compressed_pair_element() + : base_type{} {} + + template>, compressed_pair_element>>> + compressed_pair_element(Args &&args) + : base_type{std::forward(args)} {} + + template + compressed_pair_element(std::tuple args, std::index_sequence) + : base_type{std::forward(std::get(args))...} {} + + [[nodiscard]] reference get() ENTT_NOEXCEPT { + return *this; + } + + [[nodiscard]] const_reference get() const ENTT_NOEXCEPT { + return *this; + } +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @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. + * + * @tparam Dummy Dummy template parameter used for internal purposes. + */ + template && std::is_default_constructible_v>> + constexpr compressed_pair() + : 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) = 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) + : first_base{std::forward(arg)}, + second_base{std::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(std::piecewise_construct_t, std::tuple args, std::tuple other) + : first_base{std::move(args), std::index_sequence_for{}}, + second_base{std::move(other), std::index_sequence_for{}} {} + + /** + * @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) = default; + + /** + * @brief Returns the first element that a pair stores. + * @return The first element that a pair stores. + */ + [[nodiscard]] first_type &first() ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /*! @copydoc first */ + [[nodiscard]] const first_type &first() const ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /** + * @brief Returns the second element that a pair stores. + * @return The second element that a pair stores. + */ + [[nodiscard]] second_type &second() ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /*! @copydoc second */ + [[nodiscard]] const second_type &second() const ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /** + * @brief Swaps two compressed pair objects. + * @param other The compressed pair to swap with. + */ + void swap(compressed_pair &other) { + using std::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 + decltype(auto) get() ENTT_NOEXCEPT { + if constexpr(Index == 0u) { + return first(); + } else { + static_assert(Index == 1u, "Index out of bounds"); + return second(); + } + } + + /*! @copydoc get */ + template + decltype(auto) get() const ENTT_NOEXCEPT { + if constexpr(Index == 0u) { + return first(); + } else { + static_assert(Index == 1u, "Index out of bounds"); + 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, std::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 +inline void swap(compressed_pair &lhs, compressed_pair &rhs) { + lhs.swap(rhs); +} + +} // namespace entt + +// disable structured binding support for clang 6, it messes when specializing tuple_size +#if !defined __clang_major__ || __clang_major__ > 6 +namespace std { + +/** + * @brief `std::tuple_size` specialization for `compressed_pair`s. + * @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 +struct tuple_size>: integral_constant {}; + +/** + * @brief `std::tuple_element` specialization for `compressed_pair`s. + * @tparam Index The index of the type to return. + * @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 +struct tuple_element>: conditional { + static_assert(Index < 2u, "Index out of bounds"); +}; + +} // namespace std +#endif + +#endif + +// #include "../core/iterator.hpp" +#ifndef ENTT_CORE_ITERATOR_HPP +#define ENTT_CORE_ITERATOR_HPP + +#include +#include +#include +// #include "../config/config.h" + + +namespace entt { + +/** + * @brief Helper type to use as pointer with input iterators. + * @tparam Type of wrapped value. + */ +template +struct input_iterator_pointer final { + /*! @brief Pointer type. */ + using pointer = Type *; + + /*! @brief Default copy constructor, deleted on purpose. */ + input_iterator_pointer(const input_iterator_pointer &) = delete; + + /*! @brief Default move constructor. */ + input_iterator_pointer(input_iterator_pointer &&) = default; + + /** + * @brief Constructs a proxy object by move. + * @param val Value to use to initialize the proxy object. + */ + input_iterator_pointer(Type &&val) + : value{std::move(val)} {} + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This proxy object. + */ + input_iterator_pointer &operator=(const input_iterator_pointer &) = delete; + + /** + * @brief Default move assignment operator. + * @return This proxy object. + */ + input_iterator_pointer &operator=(input_iterator_pointer &&) = default; + + /** + * @brief Access operator for accessing wrapped values. + * @return A pointer to the wrapped value. + */ + [[nodiscard]] pointer operator->() ENTT_NOEXCEPT { + return std::addressof(value); + } + +private: + Type value; +}; + +/** + * @brief Utility class to create an iterable object from a pair of iterators. + * @tparam It Type of iterator. + * @tparam Sentinel Type of sentinel. + */ +template +struct iterable_adaptor final { + /*! @brief Value type. */ + using value_type = typename std::iterator_traits::value_type; + /*! @brief Iterator type. */ + using iterator = It; + /*! @brief Sentinel type. */ + using sentinel = Sentinel; + + /*! @brief Default constructor. */ + iterable_adaptor() = default; + + /** + * @brief Creates an iterable object from a pair of iterators. + * @param from Begin iterator. + * @param to End iterator. + */ + iterable_adaptor(iterator from, sentinel to) + : first{from}, + last{to} {} + + /** + * @brief Returns an iterator to the beginning. + * @return An iterator to the first element of the range. + */ + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return first; + } + + /** + * @brief Returns an iterator to the end. + * @return An iterator to the element following the last element of the + * range. + */ + [[nodiscard]] sentinel end() const ENTT_NOEXCEPT { + return last; + } + + /*! @copydoc begin */ + [[nodiscard]] iterator cbegin() const ENTT_NOEXCEPT { + return begin(); + } + + /*! @copydoc end */ + [[nodiscard]] sentinel cend() const ENTT_NOEXCEPT { + return end(); + } + +private: + It first; + Sentinel last; +}; + +} // namespace entt + +#endif + +// #include "../core/memory.hpp" +#ifndef ENTT_CORE_MEMORY_HPP +#define ENTT_CORE_MEMORY_HPP + +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" + + +namespace entt { + +/** + * @brief Unwraps fancy pointers, does nothing otherwise (waiting for C++20). + * @tparam Type Pointer type. + * @param ptr Fancy or raw pointer. + * @return A raw pointer that represents the address of the original pointer. + */ +template +[[nodiscard]] constexpr auto to_address(Type &&ptr) ENTT_NOEXCEPT { + if constexpr(std::is_pointer_v>>) { + return ptr; + } else { + return to_address(std::forward(ptr).operator->()); + } +} + +/** + * @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) ENTT_NOEXCEPT { + if constexpr(std::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) ENTT_NOEXCEPT { + if constexpr(std::allocator_traits::propagate_on_container_move_assignment::value) { + lhs = std::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) ENTT_NOEXCEPT { + ENTT_ASSERT(std::allocator_traits::propagate_on_container_swap::value || lhs == rhs, "Cannot swap the containers"); + + if constexpr(std::allocator_traits::propagate_on_container_swap::value) { + using std::swap; + swap(lhs, rhs); + } +} + +/** + * @brief Checks whether a value is a power of two or not. + * @param value A value that may or may not be a power of two. + * @return True if the value is a power of two, false otherwise. + */ +[[nodiscard]] inline constexpr bool is_power_of_two(const std::size_t value) ENTT_NOEXCEPT { + return value && ((value & (value - 1)) == 0); +} + +/** + * @brief Computes the smallest power of two greater than or equal to a value. + * @param value The value to use. + * @return The smallest power of two greater than or equal to the given value. + */ +[[nodiscard]] inline constexpr std::size_t next_power_of_two(const std::size_t value) ENTT_NOEXCEPT { + ENTT_ASSERT(value < (std::size_t{1u} << (std::numeric_limits::digits - 1)), "Numeric limits exceeded"); + std::size_t curr = value - (value != 0u); + + for(int next = 1; next < std::numeric_limits::digits; next = next * 2) { + curr |= curr >> next; + } + + return ++curr; +} + +/** + * @brief Fast module utility function (powers of two only). + * @param value A value for which to calculate the modulus. + * @param mod _Modulus_, it must be a power of two. + * @return The common remainder. + */ +[[nodiscard]] inline constexpr std::size_t fast_mod(const std::size_t value, const std::size_t mod) ENTT_NOEXCEPT { + ENTT_ASSERT(is_power_of_two(mod), "Value must be a power of two"); + return value & (mod - 1u); +} + +/** + * @brief Deleter for allocator-aware unique pointers (waiting for C++20). + * @tparam Args Types of arguments to use to construct the object. + */ +template +struct allocation_deleter: private Allocator { + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Pointer type. */ + using pointer = typename std::allocator_traits::pointer; + + /** + * @brief Inherited constructors. + * @param alloc The allocator to use. + */ + allocation_deleter(const allocator_type &alloc) + : Allocator{alloc} {} + + /** + * @brief Destroys the pointed object and deallocates its memory. + * @param ptr A valid pointer to an object of the given type. + */ + void operator()(pointer ptr) { + using alloc_traits = typename std::allocator_traits; + alloc_traits::destroy(*this, to_address(ptr)); + alloc_traits::deallocate(*this, ptr, 1u); + } +}; + +/** + * @brief Allows `std::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 +auto allocate_unique(Allocator &allocator, Args &&...args) { + static_assert(!std::is_array_v, "Array types are not supported"); + + using alloc_traits = typename std::allocator_traits::template rebind_traits; + using allocator_type = typename alloc_traits::allocator_type; + + allocator_type alloc{allocator}; + auto ptr = alloc_traits::allocate(alloc, 1u); + + ENTT_TRY { + alloc_traits::construct(alloc, to_address(ptr), std::forward(args)...); + } + ENTT_CATCH { + alloc_traits::deallocate(alloc, ptr, 1u); + ENTT_THROW; + } + + return std::unique_ptr>{ptr, alloc}; +} + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct uses_allocator_construction { + template + static constexpr auto args([[maybe_unused]] const Allocator &allocator, Params &&...params) ENTT_NOEXCEPT { + if constexpr(!std::uses_allocator_v && std::is_constructible_v) { + return std::forward_as_tuple(std::forward(params)...); + } else { + static_assert(std::uses_allocator_v, "Ill-formed request"); + + if constexpr(std::is_constructible_v) { + return std::tuple(std::allocator_arg, allocator, std::forward(params)...); + } else { + static_assert(std::is_constructible_v, "Ill-formed request"); + return std::forward_as_tuple(std::forward(params)..., allocator); + } + } + } +}; + +template +struct uses_allocator_construction> { + using type = std::pair; + + template + static constexpr auto args(const Allocator &allocator, std::piecewise_construct_t, First &&first, Second &&second) ENTT_NOEXCEPT { + return std::make_tuple( + std::piecewise_construct, + std::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, std::forward(curr)...); }, std::forward(first)), + std::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, std::forward(curr)...); }, std::forward(second))); + } + + template + static constexpr auto args(const Allocator &allocator) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::tuple<>{}, std::tuple<>{}); + } + + template + static constexpr auto args(const Allocator &allocator, First &&first, Second &&second) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::forward(first)), std::forward_as_tuple(std::forward(second))); + } + + template + static constexpr auto args(const Allocator &allocator, const std::pair &value) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(value.first), std::forward_as_tuple(value.second)); + } + + template + static constexpr auto args(const Allocator &allocator, std::pair &&value) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::move(value.first)), std::forward_as_tuple(std::move(value.second))); + } +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @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 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 The arguments needed to create an object of the given type. + */ +template +constexpr auto uses_allocator_construction_args(const Allocator &allocator, Args &&...args) ENTT_NOEXCEPT { + return internal::uses_allocator_construction::args(allocator, std::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 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 newly created object of the given type. + */ +template +constexpr Type make_obj_using_allocator(const Allocator &allocator, Args &&...args) { + return std::make_from_tuple(internal::uses_allocator_construction::args(allocator, std::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 Allocator Type of allocator used to manage memory and elements. + * @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 Allocator &allocator, Args &&...args) { + return std::apply([&](auto &&...curr) { return new(value) Type(std::forward(curr)...); }, internal::uses_allocator_construction::args(allocator, std::forward(args)...)); +} + +} // namespace entt + +#endif + +// #include "../core/type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP + +#include +#include +#include +#include +// #include "../config/config.h" + +// #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 TURN_OFF_DOXYGEN */ 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 Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; + +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; + +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; + +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; + +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::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 = std::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 Type 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 Type 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 = Type; +}; + +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; + +/** + * @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 +constexpr 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 = typename 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 = typename type_list_cat::type; + +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; + +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; +}; + +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A type list. + */ +template +using type_list_unique_t = typename type_list_unique::type; + +/** + * @brief Provides the member constant `value` 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>: std::disjunction...> {}; + +/** + * @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 = typename type_list_diff::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 value. */ + static constexpr auto value = Value; +}; + +/** + * @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 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 +constexpr 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 = typename 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 = typename value_list_cat::type; + +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::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>: std::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>: std::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 std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::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>: std::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` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; + +/*! @copydoc is_complete */ +template +struct is_complete>: std::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` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_iterator_category: std::false_type {}; + +template +struct has_iterator_category::iterator_category>>: std::true_type {}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : 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` 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 + : std::conjunction, std::negation>> {}; + +/** + * @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` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; + +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; + +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_tuple_size_value: std::false_type {}; + +template +struct has_tuple_size_value::value)>>: std::true_type {}; + +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} + +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} + +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; + } +} + +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); + } +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; + +/** + * @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 = std::remove_const_t; +}; + +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; + +/** + * @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 = typename constness_as::type; + +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); + + 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 = std::remove_pointer_t()))>; +}; + +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; + +} // namespace entt + +#endif + +// #include "fwd.hpp" +#ifndef ENTT_CONTAINER_FWD_HPP +#define ENTT_CONTAINER_FWD_HPP + +#include +#include + +namespace entt { + +template< + typename Key, + typename Type, + typename = std::hash, + typename = std::equal_to, + typename = std::allocator>> +class dense_map; + +template< + typename Type, + typename = std::hash, + typename = std::equal_to, + typename = std::allocator> +class dense_set; + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct dense_map_node final { + using value_type = std::pair; + + template + dense_map_node(const std::size_t pos, Args &&...args) + : next{pos}, + element{std::forward(args)...} {} + + template + dense_map_node(std::allocator_arg_t, const Allocator &allocator, const std::size_t pos, Args &&...args) + : next{pos}, + element{entt::make_obj_using_allocator(allocator, std::forward(args)...)} {} + + template + dense_map_node(std::allocator_arg_t, const Allocator &allocator, const dense_map_node &other) + : next{other.next}, + element{entt::make_obj_using_allocator(allocator, other.element)} {} + + template + dense_map_node(std::allocator_arg_t, const Allocator &allocator, dense_map_node &&other) + : next{other.next}, + element{entt::make_obj_using_allocator(allocator, std::move(other.element))} {} + + std::size_t next; + value_type element; +}; + +template +class dense_map_iterator final { + template + friend class dense_map_iterator; + + using first_type = decltype(std::as_const(std::declval()->element.first)); + using second_type = decltype((std::declval()->element.second)); + +public: + using value_type = std::pair; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + dense_map_iterator() ENTT_NOEXCEPT + : it{} {} + + dense_map_iterator(const It iter) ENTT_NOEXCEPT + : it{iter} {} + + template && std::is_constructible_v>> + dense_map_iterator(const dense_map_iterator &other) ENTT_NOEXCEPT + : it{other.it} {} + + dense_map_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } + + dense_map_iterator operator++(int) ENTT_NOEXCEPT { + dense_map_iterator orig = *this; + return ++(*this), orig; + } + + dense_map_iterator &operator--() ENTT_NOEXCEPT { + return --it, *this; + } + + dense_map_iterator operator--(int) ENTT_NOEXCEPT { + dense_map_iterator orig = *this; + return operator--(), orig; + } + + dense_map_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + it += value; + return *this; + } + + dense_map_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + dense_map_iterator copy = *this; + return (copy += value); + } + + dense_map_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); + } + + dense_map_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); + } + + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + return {it[value].element.first, it[value].element.second}; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return {it->element.first, it->element.second}; + } + + template + friend std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator==(const dense_map_iterator &, const dense_map_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator<(const dense_map_iterator &, const dense_map_iterator &) ENTT_NOEXCEPT; + +private: + It it; +}; + +template +[[nodiscard]] std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it - rhs.it; +} + +template +[[nodiscard]] bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +[[nodiscard]] bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it < rhs.it; +} + +template +[[nodiscard]] bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +template +[[nodiscard]] bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +template +[[nodiscard]] bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +template +class dense_map_local_iterator final { + template + friend class dense_map_local_iterator; + + using first_type = decltype(std::as_const(std::declval()->element.first)); + using second_type = decltype((std::declval()->element.second)); + +public: + using value_type = std::pair; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + dense_map_local_iterator() ENTT_NOEXCEPT + : it{}, + offset{} {} + + dense_map_local_iterator(It iter, const std::size_t pos) ENTT_NOEXCEPT + : it{iter}, + offset{pos} {} + + template && std::is_constructible_v>> + dense_map_local_iterator(const dense_map_local_iterator &other) ENTT_NOEXCEPT + : it{other.it}, + offset{other.offset} {} + + dense_map_local_iterator &operator++() ENTT_NOEXCEPT { + return offset = it[offset].next, *this; + } + + dense_map_local_iterator operator++(int) ENTT_NOEXCEPT { + dense_map_local_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return {it[offset].element.first, it[offset].element.second}; + } + + [[nodiscard]] std::size_t index() const ENTT_NOEXCEPT { + return offset; + } + +private: + It it; + std::size_t offset; +}; + +template +[[nodiscard]] bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() == rhs.index(); +} + +template +[[nodiscard]] bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @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 std::size_t minimum_capacity = 8u; + + using node_type = internal::dense_map_node; + using alloc_traits = typename std::allocator_traits; + static_assert(std::is_same_v>, "Invalid value type"); + using sparse_container_type = std::vector>; + using packed_container_type = std::vector>; + + template + [[nodiscard]] std::size_t key_to_bucket(const Other &key) const ENTT_NOEXCEPT { + return fast_mod(sparse.second()(key), bucket_count()); + } + + template + [[nodiscard]] auto constrained_find(const Other &key, std::size_t bucket) { + for(auto it = begin(bucket), last = end(bucket); it != last; ++it) { + if(packed.second()(it->first, key)) { + return begin() + static_cast(it.index()); + } + } + + return end(); + } + + template + [[nodiscard]] auto constrained_find(const Other &key, std::size_t bucket) const { + for(auto it = cbegin(bucket), last = cend(bucket); it != last; ++it) { + if(packed.second()(it->first, key)) { + return cbegin() + static_cast(it.index()); + } + } + + 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 std::make_pair(it, false); + } + + packed.first().emplace_back(sparse.first()[index], std::piecewise_construct, std::forward_as_tuple(std::forward(key)), std::forward_as_tuple(std::forward(args)...)); + sparse.first()[index] = packed.first().size() - 1u; + rehash_if_required(); + + return std::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 = std::forward(value); + return std::make_pair(it, false); + } + + packed.first().emplace_back(sparse.first()[index], std::forward(key), std::forward(value)); + sparse.first()[index] = packed.first().size() - 1u; + rehash_if_required(); + + return std::make_pair(--end(), true); + } + + void move_and_pop(const std::size_t pos) { + if(const auto last = size() - 1u; pos != last) { + packed.first()[pos] = std::move(packed.first().back()); + size_type *curr = sparse.first().data() + key_to_bucket(packed.first().back().element.first); + for(; *curr != last; curr = &packed.first()[*curr].next) {} + *curr = pos; + } + + packed.first().pop_back(); + } + + void rehash_if_required() { + if(size() > (bucket_count() * max_load_factor())) { + rehash(bucket_count() * 2u); + } + } + +public: + /*! @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 = std::pair; + /*! @brief Unsigned integer type. */ + using size_type = std::size_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 Allocator type. */ + using allocator_type = Allocator; + /*! @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 bucket_count Minimal number of buckets. + * @param allocator The allocator to use. + */ + dense_map(const size_type bucket_count, const allocator_type &allocator) + : dense_map{bucket_count, hasher{}, key_equal{}, allocator} {} + + /** + * @brief Constructs an empty container with a given allocator, hash + * function and user supplied minimal number of buckets. + * @param bucket_count Minimal number of buckets. + * @param hash Hash function to use. + * @param allocator The allocator to use. + */ + dense_map(const size_type bucket_count, const hasher &hash, const allocator_type &allocator) + : dense_map{bucket_count, 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 bucket_count 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 bucket_count, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{}) + : sparse{allocator, hash}, + packed{allocator, equal}, + threshold{default_threshold} { + rehash(bucket_count); + } + + /*! @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{std::piecewise_construct, std::forward_as_tuple(other.sparse.first(), allocator), std::forward_as_tuple(other.sparse.second())}, + packed{std::piecewise_construct, std::forward_as_tuple(other.packed.first(), allocator), std::forward_as_tuple(other.packed.second())}, + threshold{other.threshold} {} + + /*! @brief Default move constructor. */ + dense_map(dense_map &&) = 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{std::piecewise_construct, std::forward_as_tuple(std::move(other.sparse.first()), allocator), std::forward_as_tuple(std::move(other.sparse.second()))}, + packed{std::piecewise_construct, std::forward_as_tuple(std::move(other.packed.first()), allocator), std::forward_as_tuple(std::move(other.packed.second()))}, + threshold{other.threshold} {} + + /** + * @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 &&) = default; + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return sparse.first().get_allocator(); + } + + /** + * @brief Returns an iterator to the beginning. + * + * The returned iterator points to the first instance of the internal array. + * 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 ENTT_NOEXCEPT { + return packed.first().begin(); + } + + /*! @copydoc cbegin */ + [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT { + return cbegin(); + } + + /*! @copydoc begin */ + [[nodiscard]] iterator begin() ENTT_NOEXCEPT { + return packed.first().begin(); + } + + /** + * @brief Returns an iterator to the end. + * + * The returned iterator points to the element following the last instance + * of the internal array. Attempting to dereference the returned iterator + * results in undefined behavior. + * + * @return An iterator to the element following the last instance of the + * internal array. + */ + [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { + return packed.first().end(); + } + + /*! @copydoc cend */ + [[nodiscard]] const_iterator end() const ENTT_NOEXCEPT { + return cend(); + } + + /*! @copydoc end */ + [[nodiscard]] iterator end() ENTT_NOEXCEPT { + return packed.first().end(); + } + + /** + * @brief Checks whether a container is empty. + * @return True if the container is empty, false otherwise. + */ + [[nodiscard]] bool empty() const ENTT_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 ENTT_NOEXCEPT { + return packed.first().size(); + } + + /*! @brief Clears the container. */ + void clear() ENTT_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. + */ + std::pair insert(const value_type &value) { + return insert_or_do_nothing(value.first, value.second); + } + + /*! @copydoc insert */ + std::pair insert(value_type &&value) { + return insert_or_do_nothing(std::move(value.first), std::move(value.second)); + } + + /** + * @copydoc insert + * @tparam Arg Type of the key-value pair to insert into the container. + */ + template + std::enable_if_t, std::pair> + insert(Arg &&value) { + return insert_or_do_nothing(std::forward(value).first, std::forward(value).second); + } + + /** + * @brief Inserts elements into the container, if their keys do not exist. + * @tparam It Type of input iterator. + * @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. + */ + template + void insert(It first, It 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 + std::pair insert_or_assign(const key_type &key, Arg &&value) { + return insert_or_overwrite(key, std::forward(value)); + } + + /*! @copydoc insert_or_assign */ + template + std::pair insert_or_assign(key_type &&key, Arg &&value) { + return insert_or_overwrite(std::move(key), std::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 + std::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(std::forward(args).first..., std::forward(args).second...); + } else if constexpr(sizeof...(Args) == 2u) { + return insert_or_do_nothing(std::forward(args)...); + } else { + auto &node = packed.first().emplace_back(packed.first().size(), std::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 std::make_pair(it, false); + } + + std::swap(node.next, sparse.first()[index]); + rehash_if_required(); + + return std::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 + std::pair try_emplace(const key_type &key, Args &&...args) { + return insert_or_do_nothing(key, std::forward(args)...); + } + + /*! @copydoc try_emplace */ + template + std::pair try_emplace(key_type &&key, Args &&...args) { + return insert_or_do_nothing(std::move(key), std::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()[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().data() + key_to_bucket(key); *curr != (std::numeric_limits::max)(); 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 Exchanges the contents with those of a given container. + * @param other Container to exchange the content with. + */ + void swap(dense_map &other) { + using std::swap; + swap(sparse, other.sparse); + swap(packed, other.packed); + swap(threshold, other.threshold); + } + + /** + * @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 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(std::move(key)).first->second; + } + + /** + * @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 + * value. + * @tparam Other Type of the key value of an element to search for. + * @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. + */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + find(const Other &key) { + return constrained_find(key, key_to_bucket(key)); + } + + /*! @copydoc find */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + find(const Other &key) const { + return constrained_find(key, key_to_bucket(key)); + } + + /** + * @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. + * @tparam Other Type of the key value of an element to search for. + * @param key Key value of an element to search for. + * @return True if there is such an element, false otherwise. + */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + contains(const Other &key) const { + 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().begin(), 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().begin(), 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 {packed.first().begin(), (std::numeric_limits::max)()}; + } + + /** + * @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([[maybe_unused]] 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 {packed.first().begin(), (std::numeric_limits::max)()}; + } + + /** + * @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(std::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 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 count New number of buckets. + */ + void rehash(const size_type count) { + auto value = (std::max)(count, minimum_capacity); + value = (std::max)(value, static_cast(size() / max_load_factor())); + + if(const auto sz = next_power_of_two(value); sz != bucket_count()) { + sparse.first().resize(sz); + std::fill(sparse.first().begin(), sparse.first().end(), (std::numeric_limits::max)()); + + for(size_type pos{}, last = size(); pos < last; ++pos) { + const auto index = key_to_bucket(packed.first()[pos].element.first); + packed.first()[pos].next = std::exchange(sparse.first()[index], pos); + } + } + } + + /** + * @brief Reserves space for at least the specified number of elements and + * regenerates the hash table. + * @param count New number of elements. + */ + void reserve(const size_type count) { + packed.first().reserve(count); + rehash(static_cast(std::ceil(count / 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; +}; + +} // namespace entt + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace std { + +template +struct uses_allocator, Allocator> + : std::true_type {}; + +} // namespace std + +/** + * Internal details not to be documented. + * @endcond + */ + +#endif + +// #include "../container/dense_set.hpp" +#ifndef ENTT_CONTAINER_DENSE_SET_HPP +#define ENTT_CONTAINER_DENSE_SET_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/compressed_pair.hpp" + +// #include "../core/memory.hpp" + +// #include "../core/type_traits.hpp" + +// #include "fwd.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +class dense_set_iterator final { + template + friend class dense_set_iterator; + +public: + using value_type = typename It::value_type::second_type; + using pointer = const value_type *; + using reference = const value_type &; + using difference_type = std::ptrdiff_t; + using iterator_category = std::random_access_iterator_tag; + + dense_set_iterator() ENTT_NOEXCEPT + : it{} {} + + dense_set_iterator(const It iter) ENTT_NOEXCEPT + : it{iter} {} + + template && std::is_constructible_v>> + dense_set_iterator(const dense_set_iterator &other) ENTT_NOEXCEPT + : it{other.it} {} + + dense_set_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } + + dense_set_iterator operator++(int) ENTT_NOEXCEPT { + dense_set_iterator orig = *this; + return ++(*this), orig; + } + + dense_set_iterator &operator--() ENTT_NOEXCEPT { + return --it, *this; + } + + dense_set_iterator operator--(int) ENTT_NOEXCEPT { + dense_set_iterator orig = *this; + return operator--(), orig; + } + + dense_set_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + it += value; + return *this; + } + + dense_set_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + dense_set_iterator copy = *this; + return (copy += value); + } + + dense_set_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); + } + + dense_set_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); + } + + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + return it[value].second; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return std::addressof(it->second); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return *operator->(); + } + + template + friend std::ptrdiff_t operator-(const dense_set_iterator &, const dense_set_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator==(const dense_set_iterator &, const dense_set_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator<(const dense_set_iterator &, const dense_set_iterator &) ENTT_NOEXCEPT; + +private: + It it; +}; + +template +[[nodiscard]] std::ptrdiff_t operator-(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it - rhs.it; +} + +template +[[nodiscard]] bool operator==(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] bool operator!=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +[[nodiscard]] bool operator<(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it < rhs.it; +} + +template +[[nodiscard]] bool operator>(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +template +[[nodiscard]] bool operator<=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +template +[[nodiscard]] bool operator>=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +template +class dense_set_local_iterator final { + template + friend class dense_set_local_iterator; + +public: + using value_type = typename It::value_type::second_type; + using pointer = const value_type *; + using reference = const value_type &; + using difference_type = std::ptrdiff_t; + using iterator_category = std::forward_iterator_tag; + + dense_set_local_iterator() ENTT_NOEXCEPT + : it{}, + offset{} {} + + dense_set_local_iterator(It iter, const std::size_t pos) ENTT_NOEXCEPT + : it{iter}, + offset{pos} {} + + template && std::is_constructible_v>> + dense_set_local_iterator(const dense_set_local_iterator &other) ENTT_NOEXCEPT + : it{other.it}, + offset{other.offset} {} + + dense_set_local_iterator &operator++() ENTT_NOEXCEPT { + return offset = it[offset].first, *this; + } + + dense_set_local_iterator operator++(int) ENTT_NOEXCEPT { + dense_set_local_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return std::addressof(it[offset].second); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return *operator->(); + } + + [[nodiscard]] std::size_t index() const ENTT_NOEXCEPT { + return offset; + } + +private: + It it; + std::size_t offset; +}; + +template +[[nodiscard]] bool operator==(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() == rhs.index(); +} + +template +[[nodiscard]] bool operator!=(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @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 std::size_t minimum_capacity = 8u; + + using node_type = std::pair; + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using sparse_container_type = std::vector>; + using packed_container_type = std::vector>; + + template + [[nodiscard]] std::size_t value_to_bucket(const Other &value) const ENTT_NOEXCEPT { + return fast_mod(sparse.second()(value), bucket_count()); + } + + template + [[nodiscard]] auto constrained_find(const Other &value, std::size_t bucket) { + for(auto it = begin(bucket), last = end(bucket); it != last; ++it) { + if(packed.second()(*it, value)) { + return begin() + static_cast(it.index()); + } + } + + return end(); + } + + template + [[nodiscard]] auto constrained_find(const Other &value, std::size_t bucket) const { + for(auto it = cbegin(bucket), last = cend(bucket); it != last; ++it) { + if(packed.second()(*it, value)) { + return cbegin() + static_cast(it.index()); + } + } + + 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 std::make_pair(it, false); + } + + packed.first().emplace_back(sparse.first()[index], std::forward(value)); + sparse.first()[index] = packed.first().size() - 1u; + rehash_if_required(); + + return std::make_pair(--end(), true); + } + + void move_and_pop(const std::size_t pos) { + if(const auto last = size() - 1u; pos != last) { + packed.first()[pos] = std::move(packed.first().back()); + size_type *curr = sparse.first().data() + value_to_bucket(packed.first().back().second); + for(; *curr != last; curr = &packed.first()[*curr].first) {} + *curr = pos; + } + + packed.first().pop_back(); + } + + void rehash_if_required() { + if(size() > (bucket_count() * max_load_factor())) { + rehash(bucket_count() * 2u); + } + } + +public: + /*! @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 = std::size_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 Allocator type. */ + using allocator_type = Allocator; + /*! @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 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 bucket_count Minimal number of buckets. + * @param allocator The allocator to use. + */ + dense_set(const size_type bucket_count, const allocator_type &allocator) + : dense_set{bucket_count, hasher{}, key_equal{}, allocator} {} + + /** + * @brief Constructs an empty container with a given allocator, hash + * function and user supplied minimal number of buckets. + * @param bucket_count Minimal number of buckets. + * @param hash Hash function to use. + * @param allocator The allocator to use. + */ + dense_set(const size_type bucket_count, const hasher &hash, const allocator_type &allocator) + : dense_set{bucket_count, 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 bucket_count 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 bucket_count, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{}) + : sparse{allocator, hash}, + packed{allocator, equal}, + threshold{default_threshold} { + rehash(bucket_count); + } + + /*! @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{std::piecewise_construct, std::forward_as_tuple(other.sparse.first(), allocator), std::forward_as_tuple(other.sparse.second())}, + packed{std::piecewise_construct, std::forward_as_tuple(other.packed.first(), allocator), std::forward_as_tuple(other.packed.second())}, + threshold{other.threshold} {} + + /*! @brief Default move constructor. */ + dense_set(dense_set &&) = 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{std::piecewise_construct, std::forward_as_tuple(std::move(other.sparse.first()), allocator), std::forward_as_tuple(std::move(other.sparse.second()))}, + packed{std::piecewise_construct, std::forward_as_tuple(std::move(other.packed.first()), allocator), std::forward_as_tuple(std::move(other.packed.second()))}, + threshold{other.threshold} {} + + /** + * @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 &&) = default; + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return sparse.first().get_allocator(); + } + + /** + * @brief Returns an iterator to the beginning. + * + * The returned iterator points to the first instance of the internal array. + * 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 ENTT_NOEXCEPT { + return packed.first().begin(); + } + + /*! @copydoc cbegin */ + [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT { + return cbegin(); + } + + /*! @copydoc begin */ + [[nodiscard]] iterator begin() ENTT_NOEXCEPT { + return packed.first().begin(); + } + + /** + * @brief Returns an iterator to the end. + * + * The returned iterator points to the element following the last instance + * of the internal array. Attempting to dereference the returned iterator + * results in undefined behavior. + * + * @return An iterator to the element following the last instance of the + * internal array. + */ + [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { + return packed.first().end(); + } + + /*! @copydoc cend */ + [[nodiscard]] const_iterator end() const ENTT_NOEXCEPT { + return cend(); + } + + /*! @copydoc end */ + [[nodiscard]] iterator end() ENTT_NOEXCEPT { + return packed.first().end(); + } + + /** + * @brief Checks whether a container is empty. + * @return True if the container is empty, false otherwise. + */ + [[nodiscard]] bool empty() const ENTT_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 ENTT_NOEXCEPT { + return packed.first().size(); + } + + /*! @brief Clears the container. */ + void clear() ENTT_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. + */ + std::pair insert(const value_type &value) { + return insert_or_do_nothing(value); + } + + /*! @copydoc insert */ + std::pair insert(value_type &&value) { + return insert_or_do_nothing(std::move(value)); + } + + /** + * @brief Inserts elements into the container, if they do not exist. + * @tparam It Type of input iterator. + * @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. + */ + template + void insert(It first, It 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 + std::pair emplace(Args &&...args) { + if constexpr(((sizeof...(Args) == 1u) && ... && std::is_same_v>, value_type>)) { + return insert_or_do_nothing(std::forward(args)...); + } else { + auto &node = packed.first().emplace_back(std::piecewise_construct, std::make_tuple(packed.first().size()), std::forward_as_tuple(std::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 std::make_pair(it, false); + } + + std::swap(node.first, sparse.first()[index]); + rehash_if_required(); + + return std::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()[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().data() + value_to_bucket(value); *curr != (std::numeric_limits::max)(); 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 Exchanges the contents with those of a given container. + * @param other Container to exchange the content with. + */ + void swap(dense_set &other) { + using std::swap; + swap(sparse, other.sparse); + swap(packed, other.packed); + swap(threshold, other.threshold); + } + + /** + * @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. + * @tparam Other Type of an element to search for. + * @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. + */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + find(const Other &value) { + return constrained_find(value, value_to_bucket(value)); + } + + /*! @copydoc find */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + find(const Other &value) const { + return constrained_find(value, value_to_bucket(value)); + } + + /** + * @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. + * @tparam Other Type of an element to search for. + * @param value Value of an element to search for. + * @return True if there is such an element, false otherwise. + */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + contains(const Other &value) const { + 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().begin(), 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().begin(), 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 {packed.first().begin(), (std::numeric_limits::max)()}; + } + + /** + * @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([[maybe_unused]] 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 {packed.first().begin(), (std::numeric_limits::max)()}; + } + + /** + * @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(std::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 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 count New number of buckets. + */ + void rehash(const size_type count) { + auto value = (std::max)(count, minimum_capacity); + value = (std::max)(value, static_cast(size() / max_load_factor())); + + if(const auto sz = next_power_of_two(value); sz != bucket_count()) { + sparse.first().resize(sz); + std::fill(sparse.first().begin(), sparse.first().end(), (std::numeric_limits::max)()); + + for(size_type pos{}, last = size(); pos < last; ++pos) { + const auto index = value_to_bucket(packed.first()[pos].second); + packed.first()[pos].first = std::exchange(sparse.first()[index], pos); + } + } + } + + /** + * @brief Reserves space for at least the specified number of elements and + * regenerates the hash table. + * @param count New number of elements. + */ + void reserve(const size_type count) { + packed.first().reserve(count); + rehash(static_cast(std::ceil(count / 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; +}; + +} // namespace entt + +#endif + +// #include "meta.hpp" +#ifndef ENTT_META_META_HPP +#define ENTT_META_META_HPP + +#include +#include +#include +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "../core/any.hpp" +#ifndef ENTT_CORE_ANY_HPP +#define ENTT_CORE_ANY_HPP + +#include +#include +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "../core/utility.hpp" +#ifndef ENTT_CORE_UTILITY_HPP +#define ENTT_CORE_UTILITY_HPP + +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + + +namespace entt { + +/*! @brief Identity function object (waiting for C++20). */ +struct identity { + /*! @brief Indicates that this is a transparent function object. */ + using is_transparent = void; + + /** + * @brief Returns its argument unchanged. + * @tparam Type Type of the argument. + * @param value The actual argument. + * @return The submitted value as-is. + */ + template + [[nodiscard]] constexpr Type &&operator()(Type &&value) const ENTT_NOEXCEPT { + return std::forward(value); + } +}; + +/** + * @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) ENTT_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) ENTT_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. + */ + y_combinator(Func recursive) + : func{std::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 + decltype(auto) operator()(Args &&...args) const { + return func(*this, std::forward(args)...); + } + + /*! @copydoc operator()() */ + template + decltype(auto) operator()(Args &&...args) { + return func(*this, std::forward(args)...); + } + +private: + Func func; +}; + +} // namespace entt + +#endif + +// #include "fwd.hpp" +#ifndef ENTT_CORE_FWD_HPP +#define ENTT_CORE_FWD_HPP + +#include +#include +// #include "../config/config.h" + + +namespace entt { + +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<>; + +} // namespace entt + +#endif + +// #include "type_info.hpp" +#ifndef ENTT_CORE_TYPE_INFO_HPP +#define ENTT_CORE_TYPE_INFO_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/attribute.h" +#ifndef ENTT_CORE_ATTRIBUTE_H +#define ENTT_CORE_ATTRIBUTE_H + +#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 + +#endif + +// #include "fwd.hpp" + +// #include "hashed_string.hpp" +#ifndef ENTT_CORE_HASHED_STRING_HPP +#define ENTT_CORE_HASHED_STRING_HPP + +#include +#include +// #include "../config/config.h" + +// #include "fwd.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct fnv1a_traits; + +template<> +struct fnv1a_traits { + using type = std::uint32_t; + static constexpr std::uint32_t offset = 2166136261; + static constexpr std::uint32_t prime = 16777619; +}; + +template<> +struct fnv1a_traits { + using type = std::uint64_t; + static constexpr std::uint64_t offset = 14695981039346656037ull; + static constexpr std::uint64_t prime = 1099511628211ull; +}; + +template +struct basic_hashed_string { + using value_type = Char; + using size_type = std::size_t; + using hash_type = id_type; + + const value_type *repr; + size_type length; + hash_type hash; +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @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 hs_traits = internal::fnv1a_traits; + + struct const_wrapper { + // non-explicit constructor on purpose + constexpr const_wrapper(const Char *str) ENTT_NOEXCEPT: repr{str} {} + const Char *repr; + }; + + // Fowler–Noll–Vo hash function v. 1a - the good + [[nodiscard]] static constexpr auto helper(const Char *str) ENTT_NOEXCEPT { + base_type base{str, 0u, hs_traits::offset}; + + for(; str[base.length]; ++base.length) { + base.hash = (base.hash ^ static_cast(str[base.length])) * hs_traits::prime; + } + + return base; + } + + // Fowler–Noll–Vo hash function v. 1a - the good + [[nodiscard]] static constexpr auto helper(const Char *str, const std::size_t len) ENTT_NOEXCEPT { + base_type base{str, len, hs_traits::offset}; + + for(size_type pos{}; pos < len; ++pos) { + base.hash = (base.hash ^ static_cast(str[pos])) * hs_traits::prime; + } + + return base; + } + +public: + /*! @brief Character type. */ + using value_type = typename base_type::value_type; + /*! @brief Unsigned integer type. */ + using size_type = typename base_type::size_type; + /*! @brief Unsigned integer type. */ + using hash_type = typename 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) ENTT_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 + [[nodiscard]] static constexpr hash_type value(const value_type (&str)[N]) ENTT_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) ENTT_NOEXCEPT { + return basic_hashed_string{wrapper}; + } + + /*! @brief Constructs an empty hashed string. */ + constexpr basic_hashed_string() ENTT_NOEXCEPT + : base_type{} {} + + /** + * @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) ENTT_NOEXCEPT + : base_type{helper(str, len)} {} + + /** + * @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 + constexpr basic_hashed_string(const value_type (&str)[N]) ENTT_NOEXCEPT + : base_type{helper(str)} {} + + /** + * @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) ENTT_NOEXCEPT + : base_type{helper(wrapper.repr)} {} + + /** + * @brief Returns the size a hashed string. + * @return The size of the hashed string. + */ + [[nodiscard]] constexpr size_type size() const ENTT_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 ENTT_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 ENTT_NOEXCEPT { + return base_type::hash; + } + + /*! @copydoc data */ + [[nodiscard]] constexpr operator const value_type *() const ENTT_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 ENTT_NOEXCEPT { + return 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, const std::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 +basic_hashed_string(const Char (&str)[N]) -> basic_hashed_string; + +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the two hashed strings are identical, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator==(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return lhs.value() == rhs.value(); +} + +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the two hashed strings differ, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator!=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is less than the second, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator<(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return lhs.value() < rhs.value(); +} + +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is less than or equal to the second, false + * otherwise. + */ +template +[[nodiscard]] constexpr bool operator<=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return !(rhs < lhs); +} + +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is greater than the second, false + * otherwise. + */ +template +[[nodiscard]] constexpr bool operator>(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is greater than or equal to the second, + * false otherwise. + */ +template +[[nodiscard]] constexpr bool operator>=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +/*! @brief Aliases for common character types. */ +using hashed_string = basic_hashed_string; + +/*! @brief Aliases for common character types. */ +using hashed_wstring = 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]] constexpr hashed_string operator"" _hs(const char *str, std::size_t) ENTT_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]] constexpr hashed_wstring operator"" _hws(const wchar_t *str, std::size_t) ENTT_NOEXCEPT { + return hashed_wstring{str}; +} + +} // namespace literals + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +struct ENTT_API type_index final { + [[nodiscard]] static id_type next() ENTT_NOEXCEPT { + static ENTT_MAYBE_ATOMIC(id_type) value{}; + return value++; + } +}; + +template +[[nodiscard]] constexpr auto stripped_type_name() ENTT_NOEXCEPT { +#if defined ENTT_PRETTY_FUNCTION + std::string_view pretty_function{ENTT_PRETTY_FUNCTION}; + auto first = pretty_function.find_first_not_of(' ', pretty_function.find_first_of(ENTT_PRETTY_FUNCTION_PREFIX) + 1); + auto value = pretty_function.substr(first, pretty_function.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first); + return value; +#else + return std::string_view{""}; +#endif +} + +template().find_first_of('.')> +[[nodiscard]] static constexpr std::string_view type_name(int) ENTT_NOEXCEPT { + constexpr auto value = stripped_type_name(); + return value; +} + +template +[[nodiscard]] static std::string_view type_name(char) ENTT_NOEXCEPT { + static const auto value = stripped_type_name(); + return value; +} + +template().find_first_of('.')> +[[nodiscard]] static constexpr id_type type_hash(int) ENTT_NOEXCEPT { + constexpr auto stripped = stripped_type_name(); + constexpr auto value = hashed_string::value(stripped.data(), stripped.size()); + return value; +} + +template +[[nodiscard]] static id_type type_hash(char) ENTT_NOEXCEPT { + static const auto value = [](const auto stripped) { + return hashed_string::value(stripped.data(), stripped.size()); + }(stripped_type_name()); + return value; +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @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() ENTT_NOEXCEPT { + static const id_type value = internal::type_index::next(); + return value; + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const ENTT_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() ENTT_NOEXCEPT { + return internal::type_hash(0); +#else + [[nodiscard]] static constexpr id_type value() ENTT_NOEXCEPT { + return type_index::value(); +#endif + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const ENTT_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 std::string_view value() ENTT_NOEXCEPT { + return internal::type_name(0); + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator std::string_view() const ENTT_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 + constexpr type_info(std::in_place_type_t) ENTT_NOEXCEPT + : seq{type_index>>::value()}, + identifier{type_hash>>::value()}, + alias{type_name>>::value()} {} + + /** + * @brief Type index. + * @return Type index. + */ + [[nodiscard]] constexpr id_type index() const ENTT_NOEXCEPT { + return seq; + } + + /** + * @brief Type hash. + * @return Type hash. + */ + [[nodiscard]] constexpr id_type hash() const ENTT_NOEXCEPT { + return identifier; + } + + /** + * @brief Type name. + * @return Type name. + */ + [[nodiscard]] constexpr std::string_view name() const ENTT_NOEXCEPT { + return alias; + } + +private: + id_type seq; + id_type identifier; + std::string_view alias; +}; + +/** + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects are identical, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator==(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return lhs.hash() == rhs.hash(); +} + +/** + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects differ, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator!=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than the second, false otherwise. + */ +[[nodiscard]] constexpr bool operator<(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return lhs.index() < rhs.index(); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than or equal to the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator<=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(rhs < lhs); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator>(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than or equal to the second, + * false otherwise. + */ +[[nodiscard]] constexpr bool operator>=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +/** + * @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() ENTT_NOEXCEPT { + if constexpr(std::is_same_v>>) { + static type_info instance{std::in_place_type}; + return instance; + } else { + return type_id>>(); + } +} + +/*! @copydoc type_id */ +template +[[nodiscard]] const type_info &type_id(Type &&) ENTT_NOEXCEPT { + return type_id>>(); +} + +} // namespace entt + +#endif + +// #include "type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP + +#include +#include +#include +#include +// #include "../config/config.h" + +// #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 TURN_OFF_DOXYGEN */ 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 Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; + +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; + +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; + +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; + +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::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 = std::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 Type 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 Type 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 = Type; +}; + +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; + +/** + * @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 +constexpr 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 = typename 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 = typename type_list_cat::type; + +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; + +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; +}; + +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A type list. + */ +template +using type_list_unique_t = typename type_list_unique::type; + +/** + * @brief Provides the member constant `value` 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>: std::disjunction...> {}; + +/** + * @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 = typename type_list_diff::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 value. */ + static constexpr auto value = Value; +}; + +/** + * @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 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 +constexpr 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 = typename 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 = typename value_list_cat::type; + +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::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>: std::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>: std::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 std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::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>: std::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` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; + +/*! @copydoc is_complete */ +template +struct is_complete>: std::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` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_iterator_category: std::false_type {}; + +template +struct has_iterator_category::iterator_category>>: std::true_type {}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : 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` 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 + : std::conjunction, std::negation>> {}; + +/** + * @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` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; + +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; + +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_tuple_size_value: std::false_type {}; + +template +struct has_tuple_size_value::value)>>: std::true_type {}; + +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} + +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} + +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; + } +} + +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); + } +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; + +/** + * @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 = std::remove_const_t; +}; + +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; + +/** + * @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 = typename constness_as::type; + +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); + + 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 = std::remove_pointer_t()))>; +}; + +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @brief A SBO friendly, type-safe container for single values of any type. + * @tparam Len Size of the storage reserved for the small buffer optimization. + * @tparam Align Optional alignment requirement. + */ +template +class basic_any { + enum class operation : std::uint8_t { + copy, + move, + transfer, + assign, + destroy, + compare, + get + }; + + enum class policy : std::uint8_t { + owner, + ref, + cref + }; + + using storage_type = std::aligned_storage_t; + using vtable_type = const void *(const operation, const basic_any &, const void *); + + template + static constexpr bool in_situ = Len && alignof(Type) <= alignof(storage_type) && sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v; + + template + static const void *basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const basic_any &value, [[maybe_unused]] const void *other) { + static_assert(!std::is_same_v && std::is_same_v>, Type>, "Invalid type"); + const Type *element = nullptr; + + if constexpr(in_situ) { + element = value.owner() ? reinterpret_cast(&value.storage) : static_cast(value.instance); + } else { + element = static_cast(value.instance); + } + + switch(op) { + case operation::copy: + if constexpr(std::is_copy_constructible_v) { + static_cast(const_cast(other))->initialize(*element); + } + break; + case operation::move: + if constexpr(in_situ) { + if(value.owner()) { + return new(&static_cast(const_cast(other))->storage) Type{std::move(*const_cast(element))}; + } + } + + return (static_cast(const_cast(other))->instance = std::exchange(const_cast(value).instance, nullptr)); + case operation::transfer: + if constexpr(std::is_move_assignable_v) { + *const_cast(element) = std::move(*static_cast(const_cast(other))); + return other; + } + [[fallthrough]]; + case operation::assign: + if constexpr(std::is_copy_assignable_v) { + *const_cast(element) = *static_cast(other); + return other; + } + break; + case operation::destroy: + if constexpr(in_situ) { + element->~Type(); + } else if constexpr(std::is_array_v) { + delete[] element; + } else { + delete element; + } + break; + case operation::compare: + if constexpr(!std::is_function_v && !std::is_array_v && is_equality_comparable_v) { + return *static_cast(element) == *static_cast(other) ? other : nullptr; + } else { + return (element == other) ? other : nullptr; + } + case operation::get: + return element; + } + + return nullptr; + } + + template + void initialize([[maybe_unused]] Args &&...args) { + if constexpr(!std::is_void_v) { + info = &type_id>>(); + vtable = basic_vtable>>; + + if constexpr(std::is_lvalue_reference_v) { + static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v && ...), "Invalid arguments"); + mode = std::is_const_v> ? policy::cref : policy::ref; + instance = (std::addressof(args), ...); + } else if constexpr(in_situ) { + if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v) { + new(&storage) Type{std::forward(args)...}; + } else { + new(&storage) Type(std::forward(args)...); + } + } else { + if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v) { + instance = new Type{std::forward(args)...}; + } else { + instance = new Type(std::forward(args)...); + } + } + } + } + + basic_any(const basic_any &other, const policy pol) ENTT_NOEXCEPT + : instance{other.data()}, + info{other.info}, + vtable{other.vtable}, + mode{pol} {} + +public: + /*! @brief Size of the internal storage. */ + static constexpr auto length = Len; + /*! @brief Alignment requirement. */ + static constexpr auto alignment = Align; + + /*! @brief Default constructor. */ + constexpr basic_any() ENTT_NOEXCEPT + : instance{}, + info{&type_id()}, + vtable{}, + mode{policy::owner} {} + + /** + * @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(std::in_place_type_t, Args &&...args) + : basic_any{} { + initialize(std::forward(args)...); + } + + /** + * @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, basic_any>>> + basic_any(Type &&value) + : basic_any{} { + initialize>(std::forward(value)); + } + + /** + * @brief Copy constructor. + * @param other The instance to copy from. + */ + basic_any(const basic_any &other) + : basic_any{} { + if(other.vtable) { + other.vtable(operation::copy, other, this); + } + } + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_any(basic_any &&other) ENTT_NOEXCEPT + : instance{}, + info{other.info}, + vtable{other.vtable}, + mode{other.mode} { + if(other.vtable) { + other.vtable(operation::move, other, this); + } + } + + /*! @brief Frees the internal storage, whatever it means. */ + ~basic_any() { + if(vtable && owner()) { + vtable(operation::destroy, *this, nullptr); + } + } + + /** + * @brief Copy assignment operator. + * @param other The instance to copy from. + * @return This any object. + */ + basic_any &operator=(const basic_any &other) { + reset(); + + if(other.vtable) { + other.vtable(operation::copy, other, this); + } + + return *this; + } + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This any object. + */ + basic_any &operator=(basic_any &&other) ENTT_NOEXCEPT { + reset(); + + if(other.vtable) { + other.vtable(operation::move, other, this); + info = other.info; + vtable = other.vtable; + 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 + std::enable_if_t, basic_any>, basic_any &> + operator=(Type &&value) { + emplace>(std::forward(value)); + return *this; + } + + /** + * @brief Returns the object type if any, `type_id()` otherwise. + * @return The object type if any, `type_id()` otherwise. + */ + [[nodiscard]] const type_info &type() const ENTT_NOEXCEPT { + return *info; + } + + /** + * @brief Returns an opaque pointer to the contained instance. + * @return An opaque pointer the contained instance, if any. + */ + [[nodiscard]] const void *data() const ENTT_NOEXCEPT { + return vtable ? vtable(operation::get, *this, nullptr) : nullptr; + } + + /** + * @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 ENTT_NOEXCEPT { + return *info == req ? data() : nullptr; + } + + /** + * @brief Returns an opaque pointer to the contained instance. + * @return An opaque pointer the contained instance, if any. + */ + [[nodiscard]] void *data() ENTT_NOEXCEPT { + return (!vtable || mode == policy::cref) ? nullptr : const_cast(vtable(operation::get, *this, nullptr)); + } + + /** + * @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) ENTT_NOEXCEPT { + return *info == req ? data() : nullptr; + } + + /** + * @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) { + reset(); + initialize(std::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 any &other) { + if(vtable && mode != policy::cref && *info == *other.info) { + return (vtable(operation::assign, *this, other.data()) != nullptr); + } + + return false; + } + + /*! @copydoc assign */ + bool assign(any &&other) { + if(vtable && mode != policy::cref && *info == *other.info) { + if(auto *val = other.data(); val) { + return (vtable(operation::transfer, *this, val) != nullptr); + } else { + return (vtable(operation::assign, *this, std::as_const(other).data()) != nullptr); + } + } + + return false; + } + + /*! @brief Destroys contained object */ + void reset() { + if(vtable && owner()) { + vtable(operation::destroy, *this, nullptr); + } + + info = &type_id(); + vtable = nullptr; + mode = policy::owner; + } + + /** + * @brief Returns false if a wrapper is empty, true otherwise. + * @return False if the wrapper is empty, true otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return vtable != nullptr; + } + + /** + * @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. + */ + bool operator==(const basic_any &other) const ENTT_NOEXCEPT { + if(vtable && *info == *other.info) { + return (vtable(operation::compare, *this, other.data()) != nullptr); + } + + return (!vtable && !other.vtable); + } + + /** + * @brief Aliasing constructor. + * @return A wrapper that shares a reference to an unmanaged object. + */ + [[nodiscard]] basic_any as_ref() ENTT_NOEXCEPT { + return basic_any{*this, (mode == policy::cref ? policy::cref : policy::ref)}; + } + + /*! @copydoc as_ref */ + [[nodiscard]] basic_any as_ref() const ENTT_NOEXCEPT { + return basic_any{*this, policy::cref}; + } + + /** + * @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 ENTT_NOEXCEPT { + return (mode == policy::owner); + } + +private: + union { + const void *instance; + storage_type storage; + }; + const type_info *info; + vtable_type *vtable; + policy mode; +}; + +/** + * @brief Checks if two wrappers differ in their content. + * @tparam Len Size of the storage reserved for the small buffer optimization. + * @tparam Align Alignment requirement. + * @param lhs A wrapper, either empty or not. + * @param rhs A wrapper, either empty or not. + * @return True if the two wrappers differ in their content, false otherwise. + */ +template +[[nodiscard]] inline bool operator!=(const basic_any &lhs, const basic_any &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Performs type-safe access to the contained object. + * @tparam Type Type to which conversion is required. + * @tparam Len Size of the storage reserved for the small buffer optimization. + * @tparam Align Alignment requirement. + * @param data Target any object. + * @return The element converted to the requested type. + */ +template +Type any_cast(const basic_any &data) ENTT_NOEXCEPT { + const auto *const instance = any_cast>(&data); + ENTT_ASSERT(instance, "Invalid instance"); + return static_cast(*instance); +} + +/*! @copydoc any_cast */ +template +Type any_cast(basic_any &data) ENTT_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 +Type any_cast(basic_any &&data) ENTT_NOEXCEPT { + if constexpr(std::is_copy_constructible_v>>) { + if(auto *const instance = any_cast>(&data); instance) { + return static_cast(std::move(*instance)); + } else { + return any_cast(data); + } + } else { + auto *const instance = any_cast>(&data); + ENTT_ASSERT(instance, "Invalid instance"); + return static_cast(std::move(*instance)); + } +} + +/*! @copydoc any_cast */ +template +const Type *any_cast(const basic_any *data) ENTT_NOEXCEPT { + const auto &info = type_id>>(); + return static_cast(data->data(info)); +} + +/*! @copydoc any_cast */ +template +Type *any_cast(basic_any *data) ENTT_NOEXCEPT { + const auto &info = type_id>>(); + // last attempt to make wrappers for const references return their values + return static_cast(static_cast, Type> *>(data)->data(info)); +} + +/** + * @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 storage 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, std::size_t Align = basic_any::alignment, typename... Args> +basic_any make_any(Args &&...args) { + return basic_any{std::in_place_type, std::forward(args)...}; +} + +/** + * @brief Forwards its argument and avoids copies for lvalue references. + * @tparam Len Size of the storage 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, std::size_t Align = basic_any::alignment, typename Type> +basic_any forward_as_any(Type &&value) { + return basic_any{std::in_place_type, std::decay_t, Type>>, std::forward(value)}; +} + +} // namespace entt + +#endif + +// #include "../core/fwd.hpp" +#ifndef ENTT_CORE_FWD_HPP +#define ENTT_CORE_FWD_HPP + +#include +#include +// #include "../config/config.h" + + +namespace entt { + +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<>; + +} // namespace entt + +#endif + +// #include "../core/iterator.hpp" +#ifndef ENTT_CORE_ITERATOR_HPP +#define ENTT_CORE_ITERATOR_HPP + +#include +#include +#include +// #include "../config/config.h" + + +namespace entt { + +/** + * @brief Helper type to use as pointer with input iterators. + * @tparam Type of wrapped value. + */ +template +struct input_iterator_pointer final { + /*! @brief Pointer type. */ + using pointer = Type *; + + /*! @brief Default copy constructor, deleted on purpose. */ + input_iterator_pointer(const input_iterator_pointer &) = delete; + + /*! @brief Default move constructor. */ + input_iterator_pointer(input_iterator_pointer &&) = default; + + /** + * @brief Constructs a proxy object by move. + * @param val Value to use to initialize the proxy object. + */ + input_iterator_pointer(Type &&val) + : value{std::move(val)} {} + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This proxy object. + */ + input_iterator_pointer &operator=(const input_iterator_pointer &) = delete; + + /** + * @brief Default move assignment operator. + * @return This proxy object. + */ + input_iterator_pointer &operator=(input_iterator_pointer &&) = default; + + /** + * @brief Access operator for accessing wrapped values. + * @return A pointer to the wrapped value. + */ + [[nodiscard]] pointer operator->() ENTT_NOEXCEPT { + return std::addressof(value); + } + +private: + Type value; +}; + +/** + * @brief Utility class to create an iterable object from a pair of iterators. + * @tparam It Type of iterator. + * @tparam Sentinel Type of sentinel. + */ +template +struct iterable_adaptor final { + /*! @brief Value type. */ + using value_type = typename std::iterator_traits::value_type; + /*! @brief Iterator type. */ + using iterator = It; + /*! @brief Sentinel type. */ + using sentinel = Sentinel; + + /*! @brief Default constructor. */ + iterable_adaptor() = default; + + /** + * @brief Creates an iterable object from a pair of iterators. + * @param from Begin iterator. + * @param to End iterator. + */ + iterable_adaptor(iterator from, sentinel to) + : first{from}, + last{to} {} + + /** + * @brief Returns an iterator to the beginning. + * @return An iterator to the first element of the range. + */ + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return first; + } + + /** + * @brief Returns an iterator to the end. + * @return An iterator to the element following the last element of the + * range. + */ + [[nodiscard]] sentinel end() const ENTT_NOEXCEPT { + return last; + } + + /*! @copydoc begin */ + [[nodiscard]] iterator cbegin() const ENTT_NOEXCEPT { + return begin(); + } + + /*! @copydoc end */ + [[nodiscard]] sentinel cend() const ENTT_NOEXCEPT { + return end(); + } + +private: + It first; + Sentinel last; +}; + +} // namespace entt + +#endif + +// #include "../core/type_info.hpp" +#ifndef ENTT_CORE_TYPE_INFO_HPP +#define ENTT_CORE_TYPE_INFO_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/attribute.h" + +// #include "fwd.hpp" + +// #include "hashed_string.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +struct ENTT_API type_index final { + [[nodiscard]] static id_type next() ENTT_NOEXCEPT { + static ENTT_MAYBE_ATOMIC(id_type) value{}; + return value++; + } +}; + +template +[[nodiscard]] constexpr auto stripped_type_name() ENTT_NOEXCEPT { +#if defined ENTT_PRETTY_FUNCTION + std::string_view pretty_function{ENTT_PRETTY_FUNCTION}; + auto first = pretty_function.find_first_not_of(' ', pretty_function.find_first_of(ENTT_PRETTY_FUNCTION_PREFIX) + 1); + auto value = pretty_function.substr(first, pretty_function.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first); + return value; +#else + return std::string_view{""}; +#endif +} + +template().find_first_of('.')> +[[nodiscard]] static constexpr std::string_view type_name(int) ENTT_NOEXCEPT { + constexpr auto value = stripped_type_name(); + return value; +} + +template +[[nodiscard]] static std::string_view type_name(char) ENTT_NOEXCEPT { + static const auto value = stripped_type_name(); + return value; +} + +template().find_first_of('.')> +[[nodiscard]] static constexpr id_type type_hash(int) ENTT_NOEXCEPT { + constexpr auto stripped = stripped_type_name(); + constexpr auto value = hashed_string::value(stripped.data(), stripped.size()); + return value; +} + +template +[[nodiscard]] static id_type type_hash(char) ENTT_NOEXCEPT { + static const auto value = [](const auto stripped) { + return hashed_string::value(stripped.data(), stripped.size()); + }(stripped_type_name()); + return value; +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @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() ENTT_NOEXCEPT { + static const id_type value = internal::type_index::next(); + return value; + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const ENTT_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() ENTT_NOEXCEPT { + return internal::type_hash(0); +#else + [[nodiscard]] static constexpr id_type value() ENTT_NOEXCEPT { + return type_index::value(); +#endif + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const ENTT_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 std::string_view value() ENTT_NOEXCEPT { + return internal::type_name(0); + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator std::string_view() const ENTT_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 + constexpr type_info(std::in_place_type_t) ENTT_NOEXCEPT + : seq{type_index>>::value()}, + identifier{type_hash>>::value()}, + alias{type_name>>::value()} {} + + /** + * @brief Type index. + * @return Type index. + */ + [[nodiscard]] constexpr id_type index() const ENTT_NOEXCEPT { + return seq; + } + + /** + * @brief Type hash. + * @return Type hash. + */ + [[nodiscard]] constexpr id_type hash() const ENTT_NOEXCEPT { + return identifier; + } + + /** + * @brief Type name. + * @return Type name. + */ + [[nodiscard]] constexpr std::string_view name() const ENTT_NOEXCEPT { + return alias; + } + +private: + id_type seq; + id_type identifier; + std::string_view alias; +}; + +/** + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects are identical, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator==(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return lhs.hash() == rhs.hash(); +} + +/** + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects differ, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator!=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than the second, false otherwise. + */ +[[nodiscard]] constexpr bool operator<(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return lhs.index() < rhs.index(); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than or equal to the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator<=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(rhs < lhs); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator>(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than or equal to the second, + * false otherwise. + */ +[[nodiscard]] constexpr bool operator>=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +/** + * @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() ENTT_NOEXCEPT { + if constexpr(std::is_same_v>>) { + static type_info instance{std::in_place_type}; + return instance; + } else { + return type_id>>(); + } +} + +/*! @copydoc type_id */ +template +[[nodiscard]] const type_info &type_id(Type &&) ENTT_NOEXCEPT { + return type_id>>(); +} + +} // namespace entt + +#endif + +// #include "../core/type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP + +#include +#include +#include +#include +// #include "../config/config.h" + +// #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 TURN_OFF_DOXYGEN */ 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 Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; + +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; + +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; + +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; + +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::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 = std::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 Type 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 Type 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 = Type; +}; + +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; + +/** + * @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 +constexpr 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 = typename 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 = typename type_list_cat::type; + +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; + +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; +}; + +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A type list. + */ +template +using type_list_unique_t = typename type_list_unique::type; + +/** + * @brief Provides the member constant `value` 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>: std::disjunction...> {}; + +/** + * @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 = typename type_list_diff::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 value. */ + static constexpr auto value = Value; +}; + +/** + * @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 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 +constexpr 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 = typename 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 = typename value_list_cat::type; + +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::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>: std::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>: std::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 std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::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>: std::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` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; + +/*! @copydoc is_complete */ +template +struct is_complete>: std::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` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_iterator_category: std::false_type {}; + +template +struct has_iterator_category::iterator_category>>: std::true_type {}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : 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` 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 + : std::conjunction, std::negation>> {}; + +/** + * @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` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; + +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; + +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_tuple_size_value: std::false_type {}; + +template +struct has_tuple_size_value::value)>>: std::true_type {}; + +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} + +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} + +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; + } +} + +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); + } +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; + +/** + * @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 = std::remove_const_t; +}; + +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; + +/** + * @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 = typename constness_as::type; + +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); + + 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 = std::remove_pointer_t()))>; +}; + +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; + +} // namespace entt + +#endif + +// #include "../core/utility.hpp" +#ifndef ENTT_CORE_UTILITY_HPP +#define ENTT_CORE_UTILITY_HPP + +#include +// #include "../config/config.h" + + +namespace entt { + +/*! @brief Identity function object (waiting for C++20). */ +struct identity { + /*! @brief Indicates that this is a transparent function object. */ + using is_transparent = void; + + /** + * @brief Returns its argument unchanged. + * @tparam Type Type of the argument. + * @param value The actual argument. + * @return The submitted value as-is. + */ + template + [[nodiscard]] constexpr Type &&operator()(Type &&value) const ENTT_NOEXCEPT { + return std::forward(value); + } +}; + +/** + * @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) ENTT_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) ENTT_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. + */ + y_combinator(Func recursive) + : func{std::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 + decltype(auto) operator()(Args &&...args) const { + return func(*this, std::forward(args)...); + } + + /*! @copydoc operator()() */ + template + decltype(auto) operator()(Args &&...args) { + return func(*this, std::forward(args)...); + } + +private: + Func func; +}; + +} // namespace entt + +#endif + +// #include "adl_pointer.hpp" +#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 + +// #include "ctx.hpp" +#ifndef ENTT_META_CTX_HPP +#define ENTT_META_CTX_HPP + +// #include "../config/config.h" + +// #include "../core/attribute.h" +#ifndef ENTT_CORE_ATTRIBUTE_H +#define ENTT_CORE_ATTRIBUTE_H + +#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 + +#endif + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +struct meta_type_node; + +struct ENTT_API meta_context { + // we could use the lines below but VS2017 returns with an ICE if combined with ENTT_API despite the code being valid C++ + // inline static meta_type_node *local = nullptr; + // inline static meta_type_node **global = &local; + + [[nodiscard]] static meta_type_node *&local() ENTT_NOEXCEPT { + static meta_type_node *chain = nullptr; + return chain; + } + + [[nodiscard]] static meta_type_node **&global() ENTT_NOEXCEPT { + static meta_type_node **chain = &local(); + return chain; + } +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @brief Opaque container for a meta context. */ +struct meta_ctx { + /** + * @brief Binds the meta system to a given context. + * @param other A valid context to which to bind. + */ + static void bind(meta_ctx other) ENTT_NOEXCEPT { + internal::meta_context::global() = other.ctx; + } + +private: + internal::meta_type_node **ctx{&internal::meta_context::local()}; +}; + +} // namespace entt + +#endif + +// #include "fwd.hpp" +#ifndef ENTT_META_FWD_HPP +#define ENTT_META_FWD_HPP + +namespace entt { + +class meta_sequence_container; + +class meta_associative_container; + +class meta_any; + +struct meta_handle; + +struct meta_prop; + +struct meta_data; + +struct meta_func; + +class meta_type; + +} // namespace entt + +#endif + +// #include "node.hpp" +#ifndef ENTT_META_NODE_HPP +#define ENTT_META_NODE_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/attribute.h" + +// #include "../core/enum.hpp" +#ifndef ENTT_CORE_ENUM_HPP +#define ENTT_CORE_ENUM_HPP + +#include +// #include "../config/config.h" + + +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: std::false_type {}; + +/*! @copydoc enum_as_bitmask */ +template +struct enum_as_bitmask>: std::is_enum {}; + +/** + * @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; + +} // 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 std::enable_if_t, Type> +operator|(const Type lhs, const Type rhs) ENTT_NOEXCEPT { + return static_cast(static_cast>(lhs) | static_cast>(rhs)); +} + +/*! @copydoc operator| */ +template +[[nodiscard]] constexpr std::enable_if_t, Type> +operator&(const Type lhs, const Type rhs) ENTT_NOEXCEPT { + return static_cast(static_cast>(lhs) & static_cast>(rhs)); +} + +/*! @copydoc operator| */ +template +[[nodiscard]] constexpr std::enable_if_t, Type> +operator^(const Type lhs, const Type rhs) ENTT_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 std::enable_if_t, Type> +operator~(const Type value) ENTT_NOEXCEPT { + return static_cast(~static_cast>(value)); +} + +/*! @copydoc operator~ */ +template +[[nodiscard]] constexpr std::enable_if_t, bool> +operator!(const Type value) ENTT_NOEXCEPT { + return !static_cast>(value); +} + +/*! @copydoc operator| */ +template +constexpr std::enable_if_t, Type &> +operator|=(Type &lhs, const Type rhs) ENTT_NOEXCEPT { + return (lhs = (lhs | rhs)); +} + +/*! @copydoc operator| */ +template +constexpr std::enable_if_t, Type &> +operator&=(Type &lhs, const Type rhs) ENTT_NOEXCEPT { + return (lhs = (lhs & rhs)); +} + +/*! @copydoc operator| */ +template +constexpr std::enable_if_t, Type &> +operator^=(Type &lhs, const Type rhs) ENTT_NOEXCEPT { + return (lhs = (lhs ^ rhs)); +} + +#endif + +// #include "../core/fwd.hpp" + +// #include "../core/type_info.hpp" + +// #include "../core/type_traits.hpp" + +// #include "type_traits.hpp" +#ifndef ENTT_META_TYPE_TRAITS_HPP +#define ENTT_META_TYPE_TRAITS_HPP + +#include +#include + +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` to true if a given type is a + * pointer-like type from the point of view of the meta system, false otherwise. + * @tparam Type Potentially pointer-like type. + */ +template +struct is_meta_pointer_like: std::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 + + +namespace entt { + +class meta_any; +class meta_type; +struct meta_handle; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +enum class meta_traits : std::uint32_t { + is_none = 0x0000, + is_const = 0x0001, + is_static = 0x0002, + is_arithmetic = 0x0004, + is_array = 0x0008, + is_enum = 0x0010, + is_class = 0x0020, + is_pointer = 0x0040, + is_meta_pointer_like = 0x0080, + is_meta_sequence_container = 0x0100, + is_meta_associative_container = 0x0200, + _entt_enum_as_bitmask +}; + +struct meta_type_node; + +struct meta_prop_node { + meta_prop_node *next; + const meta_any &id; + meta_any &value; +}; + +struct meta_base_node { + meta_base_node *next; + meta_type_node *const type; + meta_any (*const cast)(meta_any) ENTT_NOEXCEPT; +}; + +struct meta_conv_node { + meta_conv_node *next; + meta_type_node *const type; + meta_any (*const conv)(const meta_any &); +}; + +struct meta_ctor_node { + using size_type = std::size_t; + meta_ctor_node *next; + const size_type arity; + meta_type (*const arg)(const size_type) ENTT_NOEXCEPT; + meta_any (*const invoke)(meta_any *const); +}; + +struct meta_data_node { + using size_type = std::size_t; + id_type id; + const meta_traits traits; + meta_data_node *next; + meta_prop_node *prop; + const size_type arity; + meta_type_node *const type; + meta_type (*const arg)(const size_type) ENTT_NOEXCEPT; + bool (*const set)(meta_handle, meta_any); + meta_any (*const get)(meta_handle); +}; + +struct meta_func_node { + using size_type = std::size_t; + id_type id; + const meta_traits traits; + meta_func_node *next; + meta_prop_node *prop; + const size_type arity; + meta_type_node *const ret; + meta_type (*const arg)(const size_type) ENTT_NOEXCEPT; + meta_any (*const invoke)(meta_handle, meta_any *const); +}; + +struct meta_template_node { + using size_type = std::size_t; + const size_type arity; + meta_type_node *const type; + meta_type_node *(*const arg)(const size_type)ENTT_NOEXCEPT; +}; + +struct meta_type_node { + using size_type = std::size_t; + const type_info *info; + id_type id; + const meta_traits traits; + meta_type_node *next; + meta_prop_node *prop; + const size_type size_of; + meta_type_node *(*const remove_pointer)() ENTT_NOEXCEPT; + meta_any (*const default_constructor)(); + double (*const conversion_helper)(void *, const void *); + const meta_template_node *const templ; + meta_ctor_node *ctor{nullptr}; + meta_base_node *base{nullptr}; + meta_conv_node *conv{nullptr}; + meta_data_node *data{nullptr}; + meta_func_node *func{nullptr}; + void (*dtor)(void *){nullptr}; +}; + +template +meta_type_node *meta_arg_node(type_list, const std::size_t index) ENTT_NOEXCEPT; + +template +class ENTT_API meta_node { + static_assert(std::is_same_v>>, "Invalid type"); + + [[nodiscard]] static auto *meta_default_constructor() ENTT_NOEXCEPT { + if constexpr(std::is_default_constructible_v) { + return +[]() { return meta_any{std::in_place_type}; }; + } else { + return static_cast>(nullptr); + } + } + + [[nodiscard]] static auto *meta_conversion_helper() ENTT_NOEXCEPT { + if constexpr(std::is_arithmetic_v) { + return +[](void *bin, const void *value) { + return bin ? static_cast(*static_cast(bin) = static_cast(*static_cast(value))) : static_cast(*static_cast(value)); + }; + } else if constexpr(std::is_enum_v) { + return +[](void *bin, const void *value) { + return bin ? static_cast(*static_cast(bin) = static_cast(static_cast>(*static_cast(value)))) : static_cast(*static_cast(value)); + }; + } else { + return static_cast>(nullptr); + } + } + + [[nodiscard]] static meta_template_node *meta_template_info() ENTT_NOEXCEPT { + if constexpr(is_complete_v>) { + static meta_template_node node{ + meta_template_traits::args_type::size, + meta_node::class_type>::resolve(), + [](const std::size_t index) ENTT_NOEXCEPT { return meta_arg_node(typename meta_template_traits::args_type{}, index); } + // tricks clang-format + }; + + return &node; + } else { + return nullptr; + } + } + +public: + [[nodiscard]] static meta_type_node *resolve() ENTT_NOEXCEPT { + static meta_type_node node{ + &type_id(), + {}, + internal::meta_traits::is_none + | (std::is_arithmetic_v ? internal::meta_traits::is_arithmetic : internal::meta_traits::is_none) + | (std::is_array_v ? internal::meta_traits::is_array : internal::meta_traits::is_none) + | (std::is_enum_v ? internal::meta_traits::is_enum : internal::meta_traits::is_none) + | (std::is_class_v ? internal::meta_traits::is_class : internal::meta_traits::is_none) + | (std::is_pointer_v ? internal::meta_traits::is_pointer : internal::meta_traits::is_none) + | (is_meta_pointer_like_v ? internal::meta_traits::is_meta_pointer_like : internal::meta_traits::is_none) + | (is_complete_v> ? internal::meta_traits::is_meta_sequence_container : internal::meta_traits::is_none) + | (is_complete_v> ? internal::meta_traits::is_meta_associative_container : internal::meta_traits::is_none), + nullptr, + nullptr, + size_of_v, + &meta_node>>>::resolve, + meta_default_constructor(), + meta_conversion_helper(), + meta_template_info() + // tricks clang-format + }; + + return &node; + } +}; + +template +[[nodiscard]] meta_type_node *meta_arg_node(type_list, const std::size_t index) ENTT_NOEXCEPT { + meta_type_node *args[sizeof...(Args) + 1u]{nullptr, internal::meta_node>>::resolve()...}; + return args[index + 1u]; +} + +template +[[nodiscard]] static std::decay_t().*Member)> find_by(const Type &info_or_id, const internal::meta_type_node *node) ENTT_NOEXCEPT { + for(auto *curr = node->*Member; curr; curr = curr->next) { + if constexpr(std::is_same_v) { + if(*curr->type->info == info_or_id) { + return curr; + } + } else if constexpr(std::is_same_v) { + if(curr->type->id == info_or_id) { + return curr; + } + } else { + if(curr->id == info_or_id) { + return curr; + } + } + } + + for(auto *curr = node->base; curr; curr = curr->next) { + if(auto *ret = find_by(info_or_id, curr->type); ret) { + return ret; + } + } + + return nullptr; +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +} // namespace entt + +#endif + +// #include "range.hpp" +#ifndef ENTT_META_RANGE_HPP +#define ENTT_META_RANGE_HPP + +#include +#include +// #include "../core/iterator.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct meta_range_iterator final { + using difference_type = std::ptrdiff_t; + using value_type = Type; + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + using node_type = Node; + + meta_range_iterator() ENTT_NOEXCEPT + : it{} {} + + meta_range_iterator(node_type *head) ENTT_NOEXCEPT + : it{head} {} + + meta_range_iterator &operator++() ENTT_NOEXCEPT { + return (it = it->next), *this; + } + + meta_range_iterator operator++(int) ENTT_NOEXCEPT { + meta_range_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return it; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + [[nodiscard]] bool operator==(const meta_range_iterator &other) const ENTT_NOEXCEPT { + return it == other.it; + } + + [[nodiscard]] bool operator!=(const meta_range_iterator &other) const ENTT_NOEXCEPT { + return !(*this == other); + } + +private: + node_type *it; +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Iterable range to use to iterate all types of meta objects. + * @tparam Type Type of meta objects returned. + * @tparam Node Type of meta nodes iterated. + */ +template +struct meta_range final { + /*! @brief Node type. */ + using node_type = Node; + /*! @brief Input iterator type. */ + using iterator = internal::meta_range_iterator; + /*! @brief Constant input iterator type. */ + using const_iterator = iterator; + + /*! @brief Default constructor. */ + meta_range() ENTT_NOEXCEPT = default; + + /** + * @brief Constructs a meta range from a given node. + * @param head The underlying node with which to construct the range. + */ + meta_range(node_type *head) ENTT_NOEXCEPT + : node{head} {} + + /** + * @brief Returns an iterator to the beginning. + * @return An iterator to the first meta object of the range. + */ + [[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT { + return iterator{node}; + } + + /*! @copydoc cbegin */ + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return cbegin(); + } + + /** + * @brief Returns an iterator to the end. + * @return An iterator to the element following the last meta object of the + * range. + */ + [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { + return iterator{}; + } + + /*! @copydoc cend */ + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return cend(); + } + +private: + node_type *node{nullptr}; +}; + +} // namespace entt + +#endif + +// #include "type_traits.hpp" + + +namespace entt { + +class meta_any; +class meta_type; + +/*! @brief Proxy object for sequence containers. */ +class meta_sequence_container { + class meta_iterator; + +public: + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Meta iterator type. */ + using iterator = meta_iterator; + + /*! @brief Default constructor. */ + meta_sequence_container() ENTT_NOEXCEPT = default; + + /** + * @brief Construct a proxy object for sequence containers. + * @tparam Type Type of container to wrap. + * @param instance The container to wrap. + */ + template + meta_sequence_container(std::in_place_type_t, any instance) ENTT_NOEXCEPT + : value_type_node{internal::meta_node>>::resolve()}, + size_fn{&meta_sequence_container_traits::size}, + resize_fn{&meta_sequence_container_traits::resize}, + iter_fn{&meta_sequence_container_traits::iter}, + insert_fn{&meta_sequence_container_traits::insert}, + erase_fn{&meta_sequence_container_traits::erase}, + storage{std::move(instance)} {} + + [[nodiscard]] inline meta_type value_type() const ENTT_NOEXCEPT; + [[nodiscard]] inline size_type size() const ENTT_NOEXCEPT; + inline bool resize(const size_type); + inline bool clear(); + [[nodiscard]] inline iterator begin(); + [[nodiscard]] inline iterator end(); + inline iterator insert(iterator, meta_any); + inline iterator erase(iterator); + [[nodiscard]] inline meta_any operator[](const size_type); + [[nodiscard]] inline explicit operator bool() const ENTT_NOEXCEPT; + +private: + internal::meta_type_node *value_type_node = nullptr; + size_type (*size_fn)(const any &) ENTT_NOEXCEPT = nullptr; + bool (*resize_fn)(any &, size_type) = nullptr; + iterator (*iter_fn)(any &, const bool) = nullptr; + iterator (*insert_fn)(any &, const std::ptrdiff_t, meta_any &) = nullptr; + iterator (*erase_fn)(any &, const std::ptrdiff_t) = nullptr; + any storage{}; +}; + +/*! @brief Proxy object for associative containers. */ +class meta_associative_container { + class meta_iterator; + +public: + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Meta iterator type. */ + using iterator = meta_iterator; + + /*! @brief Default constructor. */ + meta_associative_container() ENTT_NOEXCEPT = default; + + /** + * @brief Construct a proxy object for associative containers. + * @tparam Type Type of container to wrap. + * @param instance The container to wrap. + */ + template + meta_associative_container(std::in_place_type_t, any instance) ENTT_NOEXCEPT + : key_only_container{meta_associative_container_traits::key_only}, + key_type_node{internal::meta_node>>::resolve()}, + mapped_type_node{nullptr}, + value_type_node{internal::meta_node>>::resolve()}, + size_fn{&meta_associative_container_traits::size}, + clear_fn{&meta_associative_container_traits::clear}, + iter_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}, + storage{std::move(instance)} { + if constexpr(!meta_associative_container_traits::key_only) { + mapped_type_node = internal::meta_node>>::resolve(); + } + } + + [[nodiscard]] inline bool key_only() const ENTT_NOEXCEPT; + [[nodiscard]] inline meta_type key_type() const ENTT_NOEXCEPT; + [[nodiscard]] inline meta_type mapped_type() const ENTT_NOEXCEPT; + [[nodiscard]] inline meta_type value_type() const ENTT_NOEXCEPT; + [[nodiscard]] inline size_type size() const ENTT_NOEXCEPT; + inline bool clear(); + [[nodiscard]] inline iterator begin(); + [[nodiscard]] inline iterator end(); + inline bool insert(meta_any, meta_any); + inline bool erase(meta_any); + [[nodiscard]] inline iterator find(meta_any); + [[nodiscard]] inline explicit operator bool() const ENTT_NOEXCEPT; + +private: + bool key_only_container{}; + internal::meta_type_node *key_type_node = nullptr; + internal::meta_type_node *mapped_type_node = nullptr; + internal::meta_type_node *value_type_node = nullptr; + size_type (*size_fn)(const any &) ENTT_NOEXCEPT = nullptr; + bool (*clear_fn)(any &) = nullptr; + iterator (*iter_fn)(any &, const bool) = nullptr; + bool (*insert_fn)(any &, meta_any &, meta_any &) = nullptr; + bool (*erase_fn)(any &, meta_any &) = nullptr; + iterator (*find_fn)(any &, meta_any &) = nullptr; + any storage{}; +}; + +/*! @brief Opaque wrapper for values of any type. */ +class meta_any { + enum class operation : std::uint8_t { + deref, + seq, + assoc + }; + + using vtable_type = void(const operation, const any &, void *); + + template + static void basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const any &value, [[maybe_unused]] void *other) { + static_assert(std::is_same_v>, Type>, "Invalid type"); + + if constexpr(!std::is_void_v) { + switch(op) { + case operation::deref: + if constexpr(is_meta_pointer_like_v) { + if constexpr(std::is_function_v::element_type>>) { + *static_cast(other) = any_cast(value); + } else if constexpr(!std::is_same_v::element_type>, void>) { + using in_place_type = decltype(adl_meta_pointer_like::dereference(any_cast(value))); + + if constexpr(std::is_constructible_v) { + if(const auto &pointer_like = any_cast(value); pointer_like) { + static_cast(other)->emplace(adl_meta_pointer_like::dereference(pointer_like)); + } + } else { + static_cast(other)->emplace(adl_meta_pointer_like::dereference(any_cast(value))); + } + } + } + break; + case operation::seq: + if constexpr(is_complete_v>) { + *static_cast(other) = {std::in_place_type, std::move(const_cast(value))}; + } + break; + case operation::assoc: + if constexpr(is_complete_v>) { + *static_cast(other) = {std::in_place_type, std::move(const_cast(value))}; + } + break; + } + } + } + + void release() { + if(node && node->dtor && storage.owner()) { + node->dtor(storage.data()); + } + } + + meta_any(const meta_any &other, any ref) ENTT_NOEXCEPT + : storage{std::move(ref)}, + node{storage ? other.node : nullptr}, + vtable{storage ? other.vtable : &basic_vtable} {} + +public: + /*! @brief Default constructor. */ + meta_any() ENTT_NOEXCEPT + : storage{}, + node{}, + vtable{&basic_vtable} {} + + /** + * @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 meta_any(std::in_place_type_t, Args &&...args) + : storage{std::in_place_type, std::forward(args)...}, + node{internal::meta_node>>::resolve()}, + vtable{&basic_vtable>>} {} + + /** + * @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, meta_any>>> + meta_any(Type &&value) + : meta_any{std::in_place_type>>, std::forward(value)} {} + + /** + * @brief Copy constructor. + * @param other The instance to copy from. + */ + meta_any(const meta_any &other) = default; + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + meta_any(meta_any &&other) ENTT_NOEXCEPT + : storage{std::move(other.storage)}, + node{std::exchange(other.node, nullptr)}, + vtable{std::exchange(other.vtable, &basic_vtable)} {} + + /*! @brief Frees the internal storage, whatever it means. */ + ~meta_any() { + release(); + } + + /** + * @brief Copy assignment operator. + * @param other The instance to copy from. + * @return This meta any object. + */ + meta_any &operator=(const meta_any &other) { + release(); + vtable = other.vtable; + storage = other.storage; + node = other.node; + return *this; + } + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This meta any object. + */ + meta_any &operator=(meta_any &&other) ENTT_NOEXCEPT { + release(); + vtable = std::exchange(other.vtable, &basic_vtable); + storage = std::move(other.storage); + node = std::exchange(other.node, nullptr); + 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 meta any object. + */ + template + std::enable_if_t, meta_any>, meta_any &> + operator=(Type &&value) { + emplace>(std::forward(value)); + return *this; + } + + /*! @copydoc any::type */ + [[nodiscard]] inline meta_type type() const ENTT_NOEXCEPT; + + /*! @copydoc any::data */ + [[nodiscard]] const void *data() const ENTT_NOEXCEPT { + return storage.data(); + } + + /*! @copydoc any::data */ + [[nodiscard]] void *data() ENTT_NOEXCEPT { + return storage.data(); + } + + /** + * @brief Invokes the underlying function, if possible. + * + * @sa meta_func::invoke + * + * @tparam Args Types of arguments to use to invoke the function. + * @param id Unique identifier. + * @param args Parameters to use to invoke the function. + * @return A wrapper containing the returned value, if any. + */ + template + meta_any invoke(const id_type id, Args &&...args) const; + + /*! @copydoc invoke */ + template + meta_any invoke(const id_type id, Args &&...args); + + /** + * @brief Sets the value of a given variable. + * + * The type of the value is such that a cast or conversion to the type of + * the variable is possible. Otherwise, invoking the setter does nothing. + * + * @tparam Type Type of value to assign. + * @param id Unique identifier. + * @param value Parameter to use to set the underlying variable. + * @return True in case of success, false otherwise. + */ + template + bool set(const id_type id, Type &&value); + + /** + * @brief Gets the value of a given variable. + * @param id Unique identifier. + * @return A wrapper containing the value of the underlying variable. + */ + [[nodiscard]] meta_any get(const id_type id) const; + + /*! @copydoc get */ + [[nodiscard]] meta_any get(const id_type id); + + /** + * @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 { + if(const auto &info = type_id(); node && *node->info == info) { + return any_cast(&storage); + } else if(node) { + for(auto *it = node->base; it; it = it->next) { + const auto as_const = it->cast(as_ref()); + + if(const Type *base = as_const.template try_cast(); base) { + return base; + } + } + } + + return nullptr; + } + + /*! @copydoc try_cast */ + template + [[nodiscard]] Type *try_cast() { + if(const auto &info = type_id(); node && *node->info == info) { + return any_cast(&storage); + } else if(node) { + for(auto *it = node->base; it; it = it->next) { + if(Type *base = it->cast(as_ref()).template try_cast(); base) { + return base; + } + } + } + + return nullptr; + } + + /** + * @brief Tries to cast an instance to a given type. + * + * The type of the instance must be such that the cast is possible. + * + * @warning + * Attempting to perform an invalid cast results is undefined behavior. + * + * @tparam Type Type to which to cast the instance. + * @return A reference to the contained instance. + */ + template + [[nodiscard]] Type cast() const { + auto *const instance = try_cast>(); + ENTT_ASSERT(instance, "Invalid instance"); + return static_cast(*instance); + } + + /*! @copydoc cast */ + template + [[nodiscard]] Type 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 any object if there exists a viable conversion, 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 there exists a viable conversion, false otherwise. + */ + [[nodiscard]] bool allow_cast(const meta_type &type) { + if(auto other = std::as_const(*this).allow_cast(type); other) { + if(other.storage.owner()) { + std::swap(*this, other); + } + + return true; + } + + return false; + } + + /** + * @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 any object if there exists a viable conversion, an + * invalid one otherwise. + */ + template + [[nodiscard]] meta_any allow_cast() const { + const auto other = allow_cast(internal::meta_node>>::resolve()); + + if constexpr(std::is_reference_v && !std::is_const_v>) { + return other.storage.owner() ? other : meta_any{}; + } else { + return other; + } + } + + /** + * @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 there exists a viable conversion, false otherwise. + */ + template + bool allow_cast() { + if(auto other = std::as_const(*this).allow_cast(internal::meta_node>>::resolve()); other) { + if(other.storage.owner()) { + std::swap(*this, other); + return true; + } + + return (static_cast> &>(storage).data() != nullptr); + } + + return false; + } + + /*! @copydoc any::emplace */ + template + void emplace(Args &&...args) { + release(); + vtable = &basic_vtable>>; + storage.emplace(std::forward(args)...); + node = internal::meta_node>>::resolve(); + } + + /*! @copydoc any::assign */ + bool assign(const meta_any &other); + + /*! @copydoc any::assign */ + bool assign(meta_any &&other); + + /*! @copydoc any::reset */ + void reset() { + release(); + vtable = &basic_vtable; + storage.reset(); + node = nullptr; + } + + /** + * @brief Returns a sequence container proxy. + * @return A sequence container proxy for the underlying object. + */ + [[nodiscard]] meta_sequence_container as_sequence_container() ENTT_NOEXCEPT { + any detached = storage.as_ref(); + meta_sequence_container proxy; + vtable(operation::seq, detached, &proxy); + return proxy; + } + + /*! @copydoc as_sequence_container */ + [[nodiscard]] meta_sequence_container as_sequence_container() const ENTT_NOEXCEPT { + any detached = storage.as_ref(); + meta_sequence_container proxy; + vtable(operation::seq, detached, &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() ENTT_NOEXCEPT { + any detached = storage.as_ref(); + meta_associative_container proxy; + vtable(operation::assoc, detached, &proxy); + return proxy; + } + + /*! @copydoc as_associative_container */ + [[nodiscard]] meta_associative_container as_associative_container() const ENTT_NOEXCEPT { + any detached = storage.as_ref(); + meta_associative_container proxy; + vtable(operation::assoc, detached, &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*() const ENTT_NOEXCEPT { + meta_any ret{}; + vtable(operation::deref, storage, &ret); + return ret; + } + + /** + * @brief Returns false if a wrapper is invalid, true otherwise. + * @return False if the wrapper is invalid, true otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return !(node == nullptr); + } + + /*! @copydoc any::operator== */ + [[nodiscard]] bool operator==(const meta_any &other) const { + return (!node && !other.node) || (node && other.node && *node->info == *other.node->info && storage == other.storage); + } + + /*! @copydoc any::as_ref */ + [[nodiscard]] meta_any as_ref() ENTT_NOEXCEPT { + return meta_any{*this, storage.as_ref()}; + } + + /*! @copydoc any::as_ref */ + [[nodiscard]] meta_any as_ref() const ENTT_NOEXCEPT { + return meta_any{*this, storage.as_ref()}; + } + + /*! @copydoc any::owner */ + [[nodiscard]] bool owner() const ENTT_NOEXCEPT { + return storage.owner(); + } + +private: + any storage; + internal::meta_type_node *node; + vtable_type *vtable; +}; + +/** + * @brief Checks if two wrappers differ in their content. + * @param lhs A wrapper, either empty or not. + * @param rhs A wrapper, either empty or not. + * @return True if the two wrappers differ in their content, false otherwise. + */ +[[nodiscard]] inline bool operator!=(const meta_any &lhs, const meta_any &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Constructs a wrapper from a given type, passing it all arguments. + * @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. + * @return A properly initialized wrapper for an object of the given type. + */ +template +meta_any make_meta(Args &&...args) { + return meta_any{std::in_place_type, std::forward(args)...}; +} + +/** + * @brief Forwards its argument and avoids copies for lvalue references. + * @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 +meta_any forward_as_meta(Type &&value) { + return meta_any{std::in_place_type, std::decay_t, Type>>, std::forward(value)}; +} + +/** + * @brief Opaque pointers to instances of any type. + * + * A handle doesn't perform copies and isn't responsible for the contained + * object. It doesn't prolong the lifetime of the pointed instance.
+ * Handles are used to generate references to actual objects when needed. + */ +struct meta_handle { + /*! @brief Default constructor. */ + meta_handle() = default; + + /*! @brief Default copy constructor, deleted on purpose. */ + meta_handle(const meta_handle &) = delete; + + /*! @brief Default move constructor. */ + meta_handle(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 Creates a handle that points to an unmanaged object. + * @tparam Type Type of object to use to initialize the handle. + * @param value An instance of an object to use to initialize the handle. + */ + template, meta_handle>>> + meta_handle(Type &value) ENTT_NOEXCEPT + : meta_handle{} { + if constexpr(std::is_same_v, meta_any>) { + any = value.as_ref(); + } else { + any.emplace(value); + } + } + + /** + * @brief Returns false if a handle is invalid, true otherwise. + * @return False if the handle is invalid, true otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_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; + } + + /*! @copydoc operator-> */ + [[nodiscard]] const meta_any *operator->() const { + return &any; + } + +private: + meta_any any; +}; + +/*! @brief Opaque wrapper for properties of any type. */ +struct meta_prop { + /*! @brief Node type. */ + using node_type = internal::meta_prop_node; + + /** + * @brief Constructs an instance from a given node. + * @param curr The underlying node with which to construct the instance. + */ + meta_prop(const node_type *curr = nullptr) ENTT_NOEXCEPT + : node{curr} {} + + /** + * @brief Returns the stored key as a const reference. + * @return A wrapper containing the key stored with the property. + */ + [[nodiscard]] meta_any key() const { + return node->id.as_ref(); + } + + /** + * @brief Returns the stored value by copy. + * @return A wrapper containing the value stored with the property. + */ + [[nodiscard]] meta_any value() const { + return node->value; + } + + /** + * @brief Returns true if an object is valid, false otherwise. + * @return True if the object is valid, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return !(node == nullptr); + } + +private: + const node_type *node; +}; + +/*! @brief Opaque wrapper for data members. */ +struct meta_data { + /*! @brief Node type. */ + using node_type = internal::meta_data_node; + /*! @brief Unsigned integer type. */ + using size_type = typename node_type::size_type; + + /*! @copydoc meta_prop::meta_prop */ + meta_data(const node_type *curr = nullptr) ENTT_NOEXCEPT + : node{curr} {} + + /*! @copydoc meta_type::id */ + [[nodiscard]] id_type id() const ENTT_NOEXCEPT { + return node->id; + } + + /** + * @brief Returns the number of setters available. + * @return The number of setters available. + */ + [[nodiscard]] size_type arity() const ENTT_NOEXCEPT { + return node->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 ENTT_NOEXCEPT { + return !!(node->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 ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_static); + } + + /*! @copydoc meta_any::type */ + [[nodiscard]] inline meta_type type() const ENTT_NOEXCEPT; + + /** + * @brief Sets the value of a given variable. + * + * It must be possible to cast the instance to the parent type of the data + * member.
+ * The type of the value is such that a cast or conversion to the type of + * the variable is possible. Otherwise, invoking the setter does nothing. + * + * @tparam Type Type of value to assign. + * @param instance An opaque instance of the underlying type. + * @param value Parameter to use to set the underlying variable. + * @return True in case of success, false otherwise. + */ + template + bool set(meta_handle instance, Type &&value) const { + return node->set && node->set(std::move(instance), std::forward(value)); + } + + /** + * @brief Gets the value of a given variable. + * + * It must be possible to cast the instance to the parent type of the data + * member. + * + * @param instance An opaque instance of the underlying type. + * @return A wrapper containing the value of the underlying variable. + */ + [[nodiscard]] meta_any get(meta_handle instance) const { + return node->get(std::move(instance)); + } + + /** + * @brief Returns the type accepted by the i-th setter. + * @param index Index of the setter of which to return the accepted type. + * @return The type accepted by the i-th setter. + */ + [[nodiscard]] inline meta_type arg(const size_type index) const ENTT_NOEXCEPT; + + /** + * @brief Returns a range to visit registered meta properties. + * @return An iterable range to visit registered meta properties. + */ + [[nodiscard]] meta_range prop() const ENTT_NOEXCEPT { + return node->prop; + } + + /** + * @brief Lookup function for registered meta properties. + * @param key The key to use to search for a property. + * @return The registered meta property for the given key, if any. + */ + [[nodiscard]] meta_prop prop(meta_any key) const { + for(auto curr: prop()) { + if(curr.key() == key) { + return curr; + } + } + + return nullptr; + } + + /** + * @brief Returns true if an object is valid, false otherwise. + * @return True if the object is valid, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return !(node == nullptr); + } + +private: + const node_type *node; +}; + +/*! @brief Opaque wrapper for member functions. */ +struct meta_func { + /*! @brief Node type. */ + using node_type = internal::meta_func_node; + /*! @brief Unsigned integer type. */ + using size_type = typename node_type::size_type; + + /*! @copydoc meta_prop::meta_prop */ + meta_func(const node_type *curr = nullptr) ENTT_NOEXCEPT + : node{curr} {} + + /*! @copydoc meta_type::id */ + [[nodiscard]] id_type id() const ENTT_NOEXCEPT { + return node->id; + } + + /** + * @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 ENTT_NOEXCEPT { + return node->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 ENTT_NOEXCEPT { + return !!(node->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 ENTT_NOEXCEPT { + return !!(node->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 ENTT_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(const size_type index) const ENTT_NOEXCEPT; + + /** + * @brief Invokes the underlying function, if possible. + * + * To invoke a member function, the parameters must be such that a cast or + * conversion to the required types is possible. Otherwise, an empty and + * thus invalid wrapper is returned.
+ * It must be possible to cast the instance to the parent type of the member + * function. + * + * @param instance An opaque instance of the underlying type. + * @param args Parameters to use to invoke the function. + * @param sz Number of parameters to use to invoke the function. + * @return A wrapper containing the returned value, if any. + */ + meta_any invoke(meta_handle instance, meta_any *const args, const size_type sz) const { + return sz == arity() ? node->invoke(std::move(instance), args) : meta_any{}; + } + + /** + * @copybrief invoke + * + * @sa invoke + * + * @tparam Args Types of arguments to use to invoke the function. + * @param instance An opaque instance of the underlying type. + * @param args Parameters to use to invoke the function. + * @return A wrapper containing the new instance, if any. + */ + template + meta_any invoke(meta_handle instance, Args &&...args) const { + meta_any arguments[sizeof...(Args) + 1u]{std::forward(args)...}; + return invoke(std::move(instance), arguments, sizeof...(Args)); + } + + /*! @copydoc meta_data::prop */ + [[nodiscard]] meta_range prop() const ENTT_NOEXCEPT { + return node->prop; + } + + /** + * @brief Lookup function for registered meta properties. + * @param key The key to use to search for a property. + * @return The registered meta property for the given key, if any. + */ + [[nodiscard]] meta_prop prop(meta_any key) const { + for(auto curr: prop()) { + if(curr.key() == key) { + return curr; + } + } + + return nullptr; + } + + /** + * @brief Returns true if an object is valid, false otherwise. + * @return True if the object is valid, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return !(node == nullptr); + } + +private: + const node_type *node; +}; + +/*! @brief Opaque wrapper for types. */ +class meta_type { + template + [[nodiscard]] std::decay_t().*Member)> lookup(meta_any *const args, const typename internal::meta_type_node::size_type sz, Pred pred) const { + std::decay_t*Member)> candidate{}; + size_type extent{sz + 1u}; + bool ambiguous{}; + + for(auto *curr = (node->*Member); curr; curr = curr->next) { + if(pred(curr) && curr->arity == sz) { + size_type direct{}; + size_type ext{}; + + for(size_type next{}; next < sz && next == (direct + ext) && args[next]; ++next) { + const auto type = args[next].type(); + const auto other = curr->arg(next); + + if(const auto &info = other.info(); info == type.info()) { + ++direct; + } else { + ext += internal::find_by<&node_type::base>(info, type.node) + || internal::find_by<&node_type::conv>(info, type.node) + || (type.node->conversion_helper && other.node->conversion_helper); + } + } + + if((direct + ext) == sz) { + if(ext < extent) { + candidate = curr; + extent = ext; + ambiguous = false; + } else if(ext == extent) { + ambiguous = true; + } + } + } + } + + return (candidate && !ambiguous) ? candidate : decltype(candidate){}; + } + +public: + /*! @brief Node type. */ + using node_type = internal::meta_type_node; + /*! @brief Node type. */ + using base_node_type = internal::meta_base_node; + /*! @brief Unsigned integer type. */ + using size_type = typename node_type::size_type; + + /*! @copydoc meta_prop::meta_prop */ + meta_type(const node_type *curr = nullptr) ENTT_NOEXCEPT + : node{curr} {} + + /** + * @brief Constructs an instance from a given base node. + * @param curr The base node with which to construct the instance. + */ + meta_type(const base_node_type *curr) ENTT_NOEXCEPT + : node{curr ? curr->type : nullptr} {} + + /** + * @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 ENTT_NOEXCEPT { + return *node->info; + } + + /** + * @brief Returns the identifier assigned to a type. + * @return The identifier assigned to the type. + */ + [[nodiscard]] id_type id() const ENTT_NOEXCEPT { + return node->id; + } + + /** + * @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 ENTT_NOEXCEPT { + return 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 ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_arithmetic); + } + + /** + * @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 ENTT_NOEXCEPT { + return !!(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 ENTT_NOEXCEPT { + return !!(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 ENTT_NOEXCEPT { + return !!(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 ENTT_NOEXCEPT { + return !!(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 ENTT_NOEXCEPT { + return node->remove_pointer(); + } + + /** + * @brief Checks whether a type is a pointer-like type or not. + * @return True if the underlying type is a pointer-like one, false + * otherwise. + */ + [[nodiscard]] bool is_pointer_like() const ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_meta_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 ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_meta_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 ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_meta_associative_container); + } + + /** + * @brief Checks whether a type refers to a recognized class template + * specialization or not. + * @return True if the type is a recognized class template specialization, + * false otherwise. + */ + [[nodiscard]] bool is_template_specialization() const ENTT_NOEXCEPT { + return (node->templ != nullptr); + } + + /** + * @brief Returns the number of template arguments. + * @return The number of template arguments. + */ + [[nodiscard]] size_type template_arity() const ENTT_NOEXCEPT { + return node->templ ? node->templ->arity : size_type{}; + } + + /** + * @brief Returns a tag for the class template of the underlying type. + * + * @sa meta_class_template_tag + * + * @return The tag for the class template of the underlying type. + */ + [[nodiscard]] inline meta_type template_type() const ENTT_NOEXCEPT { + return node->templ ? node->templ->type : 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]] inline meta_type template_arg(const size_type index) const ENTT_NOEXCEPT { + return index < template_arity() ? node->templ->arg(index) : meta_type{}; + } + + /** + * @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 ENTT_NOEXCEPT { + return node->base; + } + + /** + * @brief Lookup function for registered base meta types. + * @param id Unique identifier. + * @return The registered base meta type for the given identifier, if any. + */ + [[nodiscard]] meta_type base(const id_type id) const { + return internal::find_by<&node_type::base>(id, node); + } + + /** + * @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 ENTT_NOEXCEPT { + return node->data; + } + + /** + * @brief Lookup function for registered meta data. + * + * Registered meta data of base classes will also be visited. + * + * @param id Unique identifier. + * @return The registered meta data for the given identifier, if any. + */ + [[nodiscard]] meta_data data(const id_type id) const { + return internal::find_by<&node_type::data>(id, node); + } + + /** + * @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 ENTT_NOEXCEPT { + return node->func; + } + + /** + * @brief Lookup function for registered meta functions. + * + * Registered meta functions of base classes will also be visited.
+ * In case of overloaded functions, the first one with the required + * identifier will be returned. + * + * @param id Unique identifier. + * @return The registered meta function for the given identifier, if any. + */ + [[nodiscard]] meta_func func(const id_type id) const { + return internal::find_by<&node_type::func>(id, node); + } + + /** + * @brief Creates an instance of the underlying type, if possible. + * + * Parameters are such that a cast or conversion to the required types is + * possible. Otherwise, an empty and thus invalid wrapper is returned.
+ * If suitable, the implicitly generated default constructor is used. + * + * @param args Parameters to use to construct the instance. + * @param sz Number of parameters to use to construct the instance. + * @return A wrapper containing the new instance, if any. + */ + [[nodiscard]] meta_any construct(meta_any *const args, const size_type sz) const { + const auto *candidate = lookup<&node_type::ctor>(args, sz, [](const auto *) { return true; }); + return candidate ? candidate->invoke(args) : ((!sz && node->default_constructor) ? node->default_constructor() : meta_any{}); + } + + /** + * @copybrief construct + * + * @sa construct + * + * @tparam Args Types of arguments to use to construct the instance. + * @param args Parameters to use to construct the instance. + * @return A wrapper containing the new instance, if any. + */ + template + [[nodiscard]] meta_any construct(Args &&...args) const { + meta_any arguments[sizeof...(Args) + 1u]{std::forward(args)...}; + return construct(arguments, sizeof...(Args)); + } + + /** + * @brief Invokes a function given an identifier, if possible. + * + * It must be possible to cast the instance to the parent type of the member + * function. + * + * @sa meta_func::invoke + * + * @param id Unique identifier. + * @param instance An opaque instance of the underlying type. + * @param args Parameters to use to invoke the function. + * @param sz Number of parameters to use to invoke the function. + * @return A wrapper containing the returned value, if any. + */ + meta_any invoke(const id_type id, meta_handle instance, meta_any *const args, const size_type sz) const { + const auto *candidate = lookup<&node_type::func>(args, sz, [id](const auto *curr) { return curr->id == id; }); + + for(auto it = base().begin(), last = base().end(); it != last && !candidate; ++it) { + candidate = it->lookup<&node_type::func>(args, sz, [id](const auto *curr) { return curr->id == id; }); + } + + return candidate ? candidate->invoke(std::move(instance), args) : meta_any{}; + } + + /** + * @copybrief invoke + * + * @sa invoke + * + * @param id Unique identifier. + * @tparam Args Types of arguments to use to invoke the function. + * @param instance An opaque instance of the underlying type. + * @param args Parameters to use to invoke the function. + * @return A wrapper containing the new instance, if any. + */ + template + meta_any invoke(const id_type id, meta_handle instance, Args &&...args) const { + meta_any arguments[sizeof...(Args) + 1u]{std::forward(args)...}; + return invoke(id, std::move(instance), arguments, sizeof...(Args)); + } + + /** + * @brief Sets the value of a given variable. + * + * It must be possible to cast the instance to the parent type of the data + * member.
+ * The type of the value is such that a cast or conversion to the type of + * the variable is possible. Otherwise, invoking the setter does nothing. + * + * @tparam Type Type of value to assign. + * @param id Unique identifier. + * @param instance An opaque instance of the underlying type. + * @param value Parameter to use to set the underlying variable. + * @return True in case of success, false otherwise. + */ + template + bool set(const id_type id, meta_handle instance, Type &&value) const { + const auto candidate = data(id); + return candidate && candidate.set(std::move(instance), std::forward(value)); + } + + /** + * @brief Gets the value of a given variable. + * + * It must be possible to cast the instance to the parent type of the data + * member. + * + * @param id Unique identifier. + * @param instance An opaque instance of the underlying type. + * @return A wrapper containing the value of the underlying variable. + */ + [[nodiscard]] meta_any get(const id_type id, meta_handle instance) const { + const auto candidate = data(id); + return candidate ? candidate.get(std::move(instance)) : meta_any{}; + } + + /** + * @brief Returns a range to visit registered top-level meta properties. + * @return An iterable range to visit registered top-level meta properties. + */ + [[nodiscard]] meta_range prop() const ENTT_NOEXCEPT { + return node->prop; + } + + /** + * @brief Lookup function for meta properties. + * + * Properties of base classes are also visited. + * + * @param key The key to use to search for a property. + * @return The registered meta property for the given key, if any. + */ + [[nodiscard]] meta_prop prop(meta_any key) const { + return internal::find_by<&internal::meta_type_node::prop>(key, node); + } + + /** + * @brief Returns true if an object is valid, false otherwise. + * @return True if the object is valid, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return !(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_type &other) const ENTT_NOEXCEPT { + return (!node && !other.node) || (node && other.node && *node->info == *other.node->info); + } + +private: + const node_type *node; +}; + +/** + * @brief Checks if two objects refer to the same type. + * @param lhs An object, either valid or not. + * @param rhs An object, either valid or not. + * @return False if the objects refer to the same node, true otherwise. + */ +[[nodiscard]] inline bool operator!=(const meta_type &lhs, const meta_type &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +[[nodiscard]] inline meta_type meta_any::type() const ENTT_NOEXCEPT { + return node; +} + +template +meta_any meta_any::invoke(const id_type id, Args &&...args) const { + return type().invoke(id, *this, std::forward(args)...); +} + +template +meta_any meta_any::invoke(const id_type id, Args &&...args) { + return type().invoke(id, *this, std::forward(args)...); +} + +template +bool meta_any::set(const id_type id, Type &&value) { + return type().set(id, *this, std::forward(value)); +} + +[[nodiscard]] inline meta_any meta_any::get(const id_type id) const { + return type().get(id, *this); +} + +[[nodiscard]] inline meta_any meta_any::get(const id_type id) { + return type().get(id, *this); +} + +[[nodiscard]] inline meta_any meta_any::allow_cast(const meta_type &type) const { + if(const auto &info = type.info(); node && *node->info == info) { + return as_ref(); + } else if(node) { + for(auto *it = node->conv; it; it = it->next) { + if(*it->type->info == info) { + return it->conv(*this); + } + } + + if(node->conversion_helper && (type.is_arithmetic() || type.is_enum())) { + // exploits the fact that arithmetic types and enums are also default constructible + auto other = type.construct(); + ENTT_ASSERT(other.node->conversion_helper, "Conversion helper not found"); + const auto value = node->conversion_helper(nullptr, storage.data()); + other.node->conversion_helper(other.storage.data(), &value); + return other; + } + + for(auto *it = node->base; it; it = it->next) { + const auto as_const = it->cast(as_ref()); + + if(auto other = as_const.allow_cast(type); other) { + return other; + } + } + } + + return {}; +} + +inline bool meta_any::assign(const meta_any &other) { + auto value = other.allow_cast(node); + return value && storage.assign(std::move(value.storage)); +} + +inline bool meta_any::assign(meta_any &&other) { + if(*node->info == *other.node->info) { + return storage.assign(std::move(other.storage)); + } + + return assign(std::as_const(other)); +} + +[[nodiscard]] inline meta_type meta_data::type() const ENTT_NOEXCEPT { + return node->type; +} + +[[nodiscard]] inline meta_type meta_func::ret() const ENTT_NOEXCEPT { + return node->ret; +} + +[[nodiscard]] inline meta_type meta_data::arg(const size_type index) const ENTT_NOEXCEPT { + return index < arity() ? node->arg(index) : meta_type{}; +} + +[[nodiscard]] inline meta_type meta_func::arg(const size_type index) const ENTT_NOEXCEPT { + return index < arity() ? node->arg(index) : meta_type{}; +} + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +class meta_sequence_container::meta_iterator final { + friend class meta_sequence_container; + + using deref_fn_type = meta_any(const any &, const std::ptrdiff_t); + + template + static meta_any deref_fn(const any &value, const std::ptrdiff_t pos) { + return meta_any{std::in_place_type::reference>, any_cast(value)[pos]}; + } + +public: + using difference_type = std::ptrdiff_t; + using value_type = meta_any; + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + meta_iterator() ENTT_NOEXCEPT + : deref{}, + offset{}, + handle{} {} + + template + explicit meta_iterator(Type &cont, const difference_type init) ENTT_NOEXCEPT + : deref{&deref_fn}, + offset{init}, + handle{cont.begin()} {} + + meta_iterator &operator++() ENTT_NOEXCEPT { + return ++offset, *this; + } + + meta_iterator operator++(int value) ENTT_NOEXCEPT { + meta_iterator orig = *this; + offset += ++value; + return orig; + } + + meta_iterator &operator--() ENTT_NOEXCEPT { + return --offset, *this; + } + + meta_iterator operator--(int value) ENTT_NOEXCEPT { + meta_iterator orig = *this; + offset -= ++value; + return orig; + } + + [[nodiscard]] reference operator*() const { + return deref(handle, offset); + } + + [[nodiscard]] pointer operator->() const { + return operator*(); + } + + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return static_cast(handle); + } + + [[nodiscard]] bool operator==(const meta_iterator &other) const ENTT_NOEXCEPT { + return offset == other.offset; + } + + [[nodiscard]] bool operator!=(const meta_iterator &other) const ENTT_NOEXCEPT { + return !(*this == other); + } + +private: + deref_fn_type *deref; + difference_type offset; + any handle; +}; + +class meta_associative_container::meta_iterator final { + enum class operation : std::uint8_t { + incr, + deref + }; + + using vtable_type = void(const operation, const any &, std::pair *); + + template + static void basic_vtable(const operation op, const any &value, std::pair *other) { + switch(op) { + case operation::incr: + ++any_cast(const_cast(value)); + break; + case operation::deref: + const auto &it = any_cast(value); + if constexpr(KeyOnly) { + other->first.emplace(*it); + } else { + other->first.emplacefirst))>(it->first); + other->second.emplacesecond))>(it->second); + } + break; + } + } + +public: + using difference_type = std::ptrdiff_t; + using value_type = std::pair; + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + meta_iterator() ENTT_NOEXCEPT + : vtable{}, + handle{} {} + + template + meta_iterator(std::integral_constant, It iter) ENTT_NOEXCEPT + : vtable{&basic_vtable}, + handle{std::move(iter)} {} + + meta_iterator &operator++() ENTT_NOEXCEPT { + vtable(operation::incr, handle, nullptr); + return *this; + } + + meta_iterator operator++(int) ENTT_NOEXCEPT { + meta_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] reference operator*() const { + reference other; + vtable(operation::deref, handle, &other); + return other; + } + + [[nodiscard]] pointer operator->() const { + return operator*(); + } + + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return static_cast(handle); + } + + [[nodiscard]] bool operator==(const meta_iterator &other) const ENTT_NOEXCEPT { + return handle == other.handle; + } + + [[nodiscard]] bool operator!=(const meta_iterator &other) const ENTT_NOEXCEPT { + return !(*this == other); + } + +private: + vtable_type *vtable; + any handle; +}; + +/** + * Internal details not to be documented. + * @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 ENTT_NOEXCEPT { + return value_type_node; +} + +/** + * @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 ENTT_NOEXCEPT { + return size_fn(storage); +} + +/** + * @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 resize_fn(storage, sz); +} + +/** + * @brief Clears the content of a container. + * @return True in case of success, false otherwise. + */ +inline bool meta_sequence_container::clear() { + return resize_fn(storage, 0u); +} + +/** + * @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 iter_fn(storage, 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 iter_fn(storage, 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(iterator it, meta_any value) { + return insert_fn(storage, it.offset, value); +} + +/** + * @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(iterator it) { + return erase_fn(storage, it.offset); +} + +/** + * @brief Returns a reference to the element at a given location of a container + * (no bounds checking is performed). + * @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 ENTT_NOEXCEPT { + return static_cast(storage); +} + +/** + * @brief Returns true if a container is also key-only, false otherwise. + * @return True if the associative container is also key-only, false otherwise. + */ +[[nodiscard]] inline bool meta_associative_container::key_only() const ENTT_NOEXCEPT { + return key_only_container; +} + +/** + * @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 ENTT_NOEXCEPT { + return key_type_node; +} + +/** + * @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 ENTT_NOEXCEPT { + return mapped_type_node; +} + +/*! @copydoc meta_sequence_container::value_type */ +[[nodiscard]] inline meta_type meta_associative_container::value_type() const ENTT_NOEXCEPT { + return value_type_node; +} + +/*! @copydoc meta_sequence_container::size */ +[[nodiscard]] inline meta_associative_container::size_type meta_associative_container::size() const ENTT_NOEXCEPT { + return size_fn(storage); +} + +/*! @copydoc meta_sequence_container::clear */ +inline bool meta_associative_container::clear() { + return clear_fn(storage); +} + +/*! @copydoc meta_sequence_container::begin */ +[[nodiscard]] inline meta_associative_container::iterator meta_associative_container::begin() { + return iter_fn(storage, false); +} + +/*! @copydoc meta_sequence_container::end */ +[[nodiscard]] inline meta_associative_container::iterator meta_associative_container::end() { + return iter_fn(storage, true); +} + +/** + * @brief Inserts an element (a key/value pair) into a container. + * @param key The key of the element to insert. + * @param value The value of the element to insert. + * @return A bool denoting whether the insertion took place. + */ +inline bool meta_associative_container::insert(meta_any key, meta_any value = {}) { + return insert_fn(storage, key, value); +} + +/** + * @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 bool meta_associative_container::erase(meta_any key) { + return erase_fn(storage, key); +} + +/** + * @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 find_fn(storage, key); +} + +/** + * @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 ENTT_NOEXCEPT { + return static_cast(storage); +} + +} // namespace entt + +#endif + +// #include "type_traits.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct is_dynamic_sequence_container: std::false_type {}; + +template +struct is_dynamic_sequence_container>: std::true_type {}; + +template +struct is_key_only_meta_associative_container: std::true_type {}; + +template +struct is_key_only_meta_associative_container>: std::false_type {}; + +template +struct basic_meta_sequence_container_traits { + using iterator = meta_sequence_container::iterator; + using size_type = std::size_t; + + [[nodiscard]] static size_type size(const any &container) ENTT_NOEXCEPT { + return any_cast(container).size(); + } + + [[nodiscard]] static bool resize([[maybe_unused]] any &container, [[maybe_unused]] size_type sz) { + if constexpr(is_dynamic_sequence_container::value) { + if(auto *const cont = any_cast(&container); cont) { + cont->resize(sz); + return true; + } + } + + return false; + } + + [[nodiscard]] static iterator iter(any &container, const bool as_end) { + if(auto *const cont = any_cast(&container); cont) { + return iterator{*cont, static_cast(as_end * cont->size())}; + } + + const Type &as_const = any_cast(container); + return iterator{as_const, static_cast(as_end * as_const.size())}; + } + + [[nodiscard]] static iterator insert([[maybe_unused]] any &container, [[maybe_unused]] const std::ptrdiff_t offset, [[maybe_unused]] meta_any &value) { + if constexpr(is_dynamic_sequence_container::value) { + if(auto *const cont = any_cast(&container); cont) { + // this abomination is necessary because only on macos value_type and const_reference are different types for std::vector + if(value.allow_cast() || value.allow_cast()) { + const auto *element = value.try_cast>(); + const auto curr = cont->insert(cont->begin() + offset, element ? *element : value.cast()); + return iterator{*cont, curr - cont->begin()}; + } + } + } + + return {}; + } + + [[nodiscard]] static iterator erase([[maybe_unused]] any &container, [[maybe_unused]] const std::ptrdiff_t offset) { + if constexpr(is_dynamic_sequence_container::value) { + if(auto *const cont = any_cast(&container); cont) { + const auto curr = cont->erase(cont->begin() + offset); + return iterator{*cont, curr - cont->begin()}; + } + } + + return {}; + } +}; + +template +struct basic_meta_associative_container_traits { + using iterator = meta_associative_container::iterator; + using size_type = std::size_t; + + static constexpr auto key_only = is_key_only_meta_associative_container::value; + + [[nodiscard]] static size_type size(const any &container) ENTT_NOEXCEPT { + return any_cast(container).size(); + } + + [[nodiscard]] static bool clear(any &container) { + if(auto *const cont = any_cast(&container); cont) { + cont->clear(); + return true; + } + + return false; + } + + [[nodiscard]] static iterator iter(any &container, const bool as_end) { + if(auto *const cont = any_cast(&container); cont) { + return iterator{std::integral_constant{}, as_end ? cont->end() : cont->begin()}; + } + + const auto &as_const = any_cast(container); + return iterator{std::integral_constant{}, as_end ? as_const.end() : as_const.begin()}; + } + + [[nodiscard]] static bool insert(any &container, meta_any &key, [[maybe_unused]] meta_any &value) { + auto *const cont = any_cast(&container); + + if constexpr(is_key_only_meta_associative_container::value) { + return cont && key.allow_cast() + && cont->insert(key.cast()).second; + } else { + return cont && key.allow_cast() && value.allow_cast() + && cont->emplace(key.cast(), value.cast()).second; + } + } + + [[nodiscard]] static bool erase(any &container, meta_any &key) { + auto *const cont = any_cast(&container); + return cont && key.allow_cast() + && (cont->erase(key.cast()) != cont->size()); + } + + [[nodiscard]] static iterator find(any &container, meta_any &key) { + if(key.allow_cast()) { + if(auto *const cont = any_cast(&container); cont) { + return iterator{std::integral_constant{}, cont->find(key.cast())}; + } + + return iterator{std::integral_constant{}, any_cast(container).find(key.cast())}; + } + + return {}; + } +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Meta sequence container traits for `std::vector`s of any type. + * @tparam Type The type of elements. + * @tparam Args Other arguments. + */ +template +struct meta_sequence_container_traits> + : internal::basic_meta_sequence_container_traits> {}; + +/** + * @brief Meta sequence container traits for `std::array`s of any type. + * @tparam Type The type of elements. + * @tparam N The number of elements. + */ +template +struct meta_sequence_container_traits> + : internal::basic_meta_sequence_container_traits> {}; + +/** + * @brief Meta associative container traits for `std::map`s of any type. + * @tparam Key The key type of elements. + * @tparam Value The value type of elements. + * @tparam Args Other arguments. + */ +template +struct meta_associative_container_traits> + : internal::basic_meta_associative_container_traits> {}; + +/** + * @brief Meta associative container traits for `std::unordered_map`s of any + * type. + * @tparam Key The key type of elements. + * @tparam Value The value type of elements. + * @tparam Args Other arguments. + */ +template +struct meta_associative_container_traits> + : internal::basic_meta_associative_container_traits> {}; + +/** + * @brief Meta associative container traits for `std::set`s of any type. + * @tparam Key The type of elements. + * @tparam Args Other arguments. + */ +template +struct meta_associative_container_traits> + : internal::basic_meta_associative_container_traits> {}; + +/** + * @brief Meta associative container traits for `std::unordered_set`s of any + * type. + * @tparam Key The type of elements. + * @tparam Args Other arguments. + */ +template +struct meta_associative_container_traits> + : internal::basic_meta_associative_container_traits> {}; + +/** + * @brief Meta associative container traits for `dense_map`s of any type. + * @tparam Key The key type of the elements. + * @tparam Type The value type of the elements. + * @tparam Args Other arguments. + */ +template +struct meta_associative_container_traits> + : internal::basic_meta_associative_container_traits> {}; + +/** + * @brief Meta associative container traits for `dense_set`s of any type. + * @tparam Type The value type of the elements. + * @tparam Args Other arguments. + */ +template +struct meta_associative_container_traits> + : internal::basic_meta_associative_container_traits> {}; + +} // namespace entt + +#endif + +// #include "meta/ctx.hpp" +#ifndef ENTT_META_CTX_HPP +#define ENTT_META_CTX_HPP + +// #include "../config/config.h" + +// #include "../core/attribute.h" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +struct meta_type_node; + +struct ENTT_API meta_context { + // we could use the lines below but VS2017 returns with an ICE if combined with ENTT_API despite the code being valid C++ + // inline static meta_type_node *local = nullptr; + // inline static meta_type_node **global = &local; + + [[nodiscard]] static meta_type_node *&local() ENTT_NOEXCEPT { + static meta_type_node *chain = nullptr; + return chain; + } + + [[nodiscard]] static meta_type_node **&global() ENTT_NOEXCEPT { + static meta_type_node **chain = &local(); + return chain; + } +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @brief Opaque container for a meta context. */ +struct meta_ctx { + /** + * @brief Binds the meta system to a given context. + * @param other A valid context to which to bind. + */ + static void bind(meta_ctx other) ENTT_NOEXCEPT { + internal::meta_context::global() = other.ctx; + } + +private: + internal::meta_type_node **ctx{&internal::meta_context::local()}; +}; + +} // namespace entt + +#endif + +// #include "meta/factory.hpp" +#ifndef ENTT_META_FACTORY_HPP +#define ENTT_META_FACTORY_HPP + +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/fwd.hpp" + +// #include "../core/type_info.hpp" + +// #include "../core/type_traits.hpp" + +// #include "meta.hpp" + +// #include "node.hpp" + +// #include "policy.hpp" +#ifndef ENTT_META_POLICY_HPP +#define ENTT_META_POLICY_HPP + +#include + +namespace entt { + +/*! @brief Empty class type used to request the _as ref_ policy. */ +struct as_ref_t { + /** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + template + static constexpr bool value = std::is_reference_v && !std::is_const_v>; + /** + * Internal details not to be documented. + * @endcond + */ +}; + +/*! @brief Empty class type used to request the _as cref_ policy. */ +struct as_cref_t { + /** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + template + static constexpr bool value = std::is_reference_v; + /** + * Internal details not to be documented. + * @endcond + */ +}; + +/*! @brief Empty class type used to request the _as-is_ policy. */ +struct as_is_t { + /** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + template + static constexpr bool value = true; + /** + * Internal details not to be documented. + * @endcond + */ +}; + +/*! @brief Empty class type used to request the _as void_ policy. */ +struct as_void_t { + /** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + template + static constexpr bool value = true; + /** + * Internal details not to be documented. + * @endcond + */ +}; + +} // namespace entt + +#endif + +// #include "range.hpp" + +// #include "utility.hpp" +#ifndef ENTT_META_UTILITY_HPP +#define ENTT_META_UTILITY_HPP + +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/type_traits.hpp" + +// #include "meta.hpp" + +// #include "node.hpp" + +// #include "policy.hpp" + + +namespace entt { + +/*! @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 { + /*! @brief Meta function return type. */ + using return_type = Ret; + /*! @brief Meta function arguments. */ + using args_type = std::conditional_t, type_list, type_list>; + + /*! @brief True if the meta function is const, false otherwise. */ + static constexpr auto is_const = true; + /*! @brief True if the meta function is static, false otherwise. */ + static constexpr auto is_static = !std::is_base_of_v; +}; + +/** + * @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 { + /*! @brief Meta function return type. */ + using return_type = Ret; + /*! @brief Meta function arguments. */ + using args_type = std::conditional_t, type_list, type_list>; + + /*! @brief True if the meta function is const, false otherwise. */ + static constexpr auto is_const = false; + /*! @brief True if the meta function is static, false otherwise. */ + static constexpr auto is_static = !std::is_base_of_v; +}; + +/** + * @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 { + /*! @brief Meta data return type. */ + using return_type = Ret &; + /*! @brief Meta data arguments. */ + using args_type = std::conditional_t, type_list<>, type_list>; + + /*! @brief True if the meta data is const, false otherwise. */ + static constexpr auto is_const = false; + /*! @brief True if the meta data is static, false otherwise. */ + static constexpr auto is_static = !std::is_base_of_v; +}; + +/** + * @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 { + /*! @brief Meta function return type. */ + using return_type = Ret; + /*! @brief Meta function arguments. */ + using args_type = std::conditional_t>, Type>, type_list, type_list>; + + /*! @brief True if the meta function is const, false otherwise. */ + static constexpr auto is_const = std::is_base_of_v>, Type> && std::is_const_v>; + /*! @brief True if the meta function is static, false otherwise. */ + static constexpr auto is_static = !std::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 { + /*! @brief Meta function return type. */ + using return_type = Ret; + /*! @brief Meta function arguments. */ + using args_type = type_list<>; + + /*! @brief True if the meta function is const, false otherwise. */ + static constexpr auto is_const = false; + /*! @brief True if the meta function is static, false otherwise. */ + static constexpr auto is_static = true; +}; + +/** + * @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 constexpr meta_function_descriptor get_rid_of_noexcept(Ret (Class::*)(Args...) const); + + template + static constexpr meta_function_descriptor get_rid_of_noexcept(Ret (Class::*)(Args...)); + + template + static constexpr meta_function_descriptor get_rid_of_noexcept(Ret Class::*); + + template + static constexpr meta_function_descriptor get_rid_of_noexcept(Ret (*)(Args...)); + + template + static constexpr 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(std::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 = typename meta_function_helper::type; + +/** + * @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 +meta_any meta_dispatch([[maybe_unused]] Type &&value) { + if constexpr(std::is_same_v) { + return meta_any{std::in_place_type}; + } else if constexpr(std::is_same_v) { + return meta_any{std::in_place_type, std::forward(value)}; + } else if constexpr(std::is_same_v) { + static_assert(std::is_lvalue_reference_v, "Invalid type"); + return meta_any{std::in_place_type &>, std::as_const(value)}; + } else { + static_assert(std::is_same_v, "Policy not supported"); + return meta_any{std::forward(value)}; + } +} + +/** + * @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. + * @return The meta type of the i-th element of the list of arguments. + */ +template +[[nodiscard]] static meta_type meta_arg(const std::size_t index) ENTT_NOEXCEPT { + return internal::meta_arg_node(Type{}, 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 value Parameter 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 value) { + if constexpr(!std::is_same_v && !std::is_same_v) { + if constexpr(std::is_member_function_pointer_v || std::is_function_v>>) { + using descriptor = meta_function_helper_t; + using data_type = type_list_element_t; + + if(auto *const clazz = instance->try_cast(); clazz && value.allow_cast()) { + std::invoke(Data, *clazz, value.cast()); + return true; + } + } else if constexpr(std::is_member_object_pointer_v) { + using data_type = std::remove_reference_t::return_type>; + + if constexpr(!std::is_array_v && !std::is_const_v) { + if(auto *const clazz = instance->try_cast(); clazz && value.allow_cast()) { + std::invoke(Data, *clazz) = value.cast(); + return true; + } + } + } else { + using data_type = std::remove_reference_t; + + if constexpr(!std::is_array_v && !std::is_const_v) { + if(value.allow_cast()) { + *Data = value.cast(); + return true; + } + } + } + } + + return false; +} + +/** + * @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([[maybe_unused]] meta_handle instance) { + if constexpr(std::is_member_pointer_v || std::is_function_v>>) { + if constexpr(!std::is_array_v>>>) { + if constexpr(std::is_invocable_v) { + if(auto *clazz = instance->try_cast(); clazz) { + return meta_dispatch(std::invoke(Data, *clazz)); + } + } + + if constexpr(std::is_invocable_v) { + if(auto *fallback = instance->try_cast(); fallback) { + return meta_dispatch(std::invoke(Data, *fallback)); + } + } + } + + return meta_any{}; + } else if constexpr(std::is_pointer_v) { + if constexpr(std::is_array_v>) { + return meta_any{}; + } else { + return meta_dispatch(*Data); + } + } else { + return meta_dispatch(Data); + } +} + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +[[nodiscard]] meta_any meta_invoke_with_args(Candidate &&candidate, Args &&...args) { + if constexpr(std::is_same_v, void>) { + std::invoke(candidate, args...); + return meta_any{std::in_place_type}; + } else { + return meta_dispatch(std::invoke(candidate, args...)); + } +} + +template +[[nodiscard]] meta_any meta_invoke([[maybe_unused]] meta_handle instance, Candidate &&candidate, [[maybe_unused]] meta_any *args, std::index_sequence) { + using descriptor = meta_function_helper_t>; + + if constexpr(std::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(std::forward(candidate), *clazz, (args + Index)->cast>()...); + } + } else if constexpr(std::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(std::forward(candidate), *clazz, (args + Index)->cast>()...); + } + } else { + if(((args + Index)->allow_cast>() && ...)) { + return meta_invoke_with_args(std::forward(candidate), (args + Index)->cast>()...); + } + } + + return meta_any{}; +} + +template +[[nodiscard]] meta_any meta_construct(meta_any *const args, std::index_sequence) { + if(((args + Index)->allow_cast() && ...)) { + return meta_any{std::in_place_type, (args + Index)->cast()...}; + } + + return meta_any{}; +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @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([[maybe_unused]] meta_handle instance, Candidate &&candidate, [[maybe_unused]] meta_any *const args) { + return internal::meta_invoke(std::move(instance), std::forward(candidate), args, std::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(std::move(instance), Candidate, args, std::make_index_sequence>::args_type::size>{}); +} + +/** + * @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 internal::meta_construct(args, std::index_sequence_for{}); +} + +/** + * @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 args Parameters to use to _invoke_ the object. + * @param candidate The actual object to _invoke_. + * @return A meta any containing the returned value, if any. + */ +template +[[nodiscard]] meta_any meta_construct(Candidate &&candidate, meta_any *const args) { + if constexpr(meta_function_helper_t::is_static) { + return internal::meta_invoke({}, std::forward(candidate), args, std::make_index_sequence>::args_type::size>{}); + } else { + return internal::meta_invoke(*args, std::forward(candidate), args + 1u, std::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 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(Candidate, args); +} + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @brief Meta factory to be used for reflection purposes. + * + * The meta factory is an utility class used to reflect types, data members and + * functions of all sorts. This class ensures that the underlying web of types + * is built correctly and performs some checks in debug mode to ensure that + * there are no subtle errors at runtime. + */ +template +class meta_factory; + +/** + * @brief Extended meta factory to be used for reflection purposes. + * @tparam Type Reflected type for which the factory was created. + * @tparam Spec Property specialization pack used to disambiguate overloads. + */ +template +class meta_factory: public meta_factory { + void link_prop_if_required(internal::meta_prop_node &node) ENTT_NOEXCEPT { + if(meta_range range{*ref}; std::find(range.cbegin(), range.cend(), &node) == range.cend()) { + ENTT_ASSERT(std::find_if(range.cbegin(), range.cend(), [&node](const auto *curr) { return curr->id == node.id; }) == range.cend(), "Duplicate identifier"); + node.next = *ref; + *ref = &node; + } + } + + template + void unroll(choice_t<2>, std::tuple property, Other &&...other) ENTT_NOEXCEPT { + std::apply([this](auto &&...curr) { (this->unroll(choice<2>, std::forward(curr)...)); }, property); + unroll(choice<2>, std::forward(other)...); + } + + template + void unroll(choice_t<1>, std::pair property, Other &&...other) ENTT_NOEXCEPT { + assign(std::move(property.first), std::move(property.second)); + unroll(choice<2>, std::forward(other)...); + } + + template + void unroll(choice_t<0>, Property &&property, Other &&...other) ENTT_NOEXCEPT { + assign(std::forward(property)); + unroll(choice<2>, std::forward(other)...); + } + + template + void unroll(choice_t<0>) ENTT_NOEXCEPT {} + + template + void assign(meta_any key, meta_any value = {}) { + static meta_any property[2u]{}; + + static internal::meta_prop_node node{ + nullptr, + property[0u], + property[1u] + // tricks clang-format + }; + + property[0u] = std::move(key); + property[1u] = std::move(value); + + link_prop_if_required(node); + } + +public: + /** + * @brief Constructs an extended factory from a given node. + * @param target The underlying node to which to assign the properties. + */ + meta_factory(internal::meta_prop_node **target) ENTT_NOEXCEPT + : ref{target} {} + + /** + * @brief Assigns a property to the last meta object created. + * + * Both the key and the value (if any) must be at least copy constructible. + * + * @tparam PropertyOrKey Type of the property or property key. + * @tparam Value Optional type of the property value. + * @param property_or_key Property or property key. + * @param value Optional property value. + * @return A meta factory for the parent type. + */ + template + meta_factory prop(PropertyOrKey &&property_or_key, Value &&...value) { + if constexpr(sizeof...(Value) == 0) { + unroll(choice<2>, std::forward(property_or_key)); + } else { + assign(std::forward(property_or_key), std::forward(value)...); + } + + return {}; + } + + /** + * @brief Assigns properties to the last meta object created. + * + * Both key and value (if any) must be at least copy constructible. + * + * @tparam Property Types of the properties. + * @param property Properties to assign to the last meta object created. + * @return A meta factory for the parent type. + */ + template + meta_factory props(Property... property) { + unroll(choice<2>, std::forward(property)...); + return {}; + } + +private: + internal::meta_prop_node **ref; +}; + +/** + * @brief Basic meta factory to be used for reflection purposes. + * @tparam Type Reflected type for which the factory was created. + */ +template +class meta_factory { + void link_base_if_required(internal::meta_base_node &node) ENTT_NOEXCEPT { + if(meta_range range{owner->base}; std::find(range.cbegin(), range.cend(), &node) == range.cend()) { + node.next = owner->base; + owner->base = &node; + } + } + + void link_conv_if_required(internal::meta_conv_node &node) ENTT_NOEXCEPT { + if(meta_range range{owner->conv}; std::find(range.cbegin(), range.cend(), &node) == range.cend()) { + node.next = owner->conv; + owner->conv = &node; + } + } + + void link_ctor_if_required(internal::meta_ctor_node &node) ENTT_NOEXCEPT { + if(meta_range range{owner->ctor}; std::find(range.cbegin(), range.cend(), &node) == range.cend()) { + node.next = owner->ctor; + owner->ctor = &node; + } + } + + void link_data_if_required(const id_type id, internal::meta_data_node &node) ENTT_NOEXCEPT { + meta_range range{owner->data}; + ENTT_ASSERT(std::find_if(range.cbegin(), range.cend(), [id, &node](const auto *curr) { return curr != &node && curr->id == id; }) == range.cend(), "Duplicate identifier"); + node.id = id; + + if(std::find(range.cbegin(), range.cend(), &node) == range.cend()) { + node.next = owner->data; + owner->data = &node; + } + } + + void link_func_if_required(const id_type id, internal::meta_func_node &node) ENTT_NOEXCEPT { + node.id = id; + + if(meta_range range{owner->func}; std::find(range.cbegin(), range.cend(), &node) == range.cend()) { + node.next = owner->func; + owner->func = &node; + } + } + + template + auto data(const id_type id, std::index_sequence) ENTT_NOEXCEPT { + using data_type = std::invoke_result_t; + using args_type = type_list)>::args_type...>; + static_assert(Policy::template value, "Invalid return type for the given policy"); + + static internal::meta_data_node node{ + {}, + /* this is never static */ + (std::is_member_object_pointer_v)> && ... && std::is_const_v>) ? internal::meta_traits::is_const : internal::meta_traits::is_none, + nullptr, + nullptr, + Setter::size, + internal::meta_node>>::resolve(), + &meta_arg::size != 1u, type_list_element_t>...>>, + [](meta_handle instance, meta_any value) -> bool { return (meta_setter>(*instance.operator->(), value.as_ref()) || ...); }, + &meta_getter + // tricks clang-format + }; + + link_data_if_required(id, node); + return meta_factory>{&node.prop}; + } + +public: + /*! @brief Default constructor. */ + meta_factory() ENTT_NOEXCEPT + : owner{internal::meta_node::resolve()} {} + + /** + * @brief Makes a meta type _searchable_. + * @param id Optional unique identifier. + * @return An extended meta factory for the given type. + */ + auto type(const id_type id = type_hash::value()) ENTT_NOEXCEPT { + meta_range range{*internal::meta_context::global()}; + ENTT_ASSERT(std::find_if(range.cbegin(), range.cend(), [id, this](const auto *curr) { return curr != owner && curr->id == id; }) == range.cend(), "Duplicate identifier"); + owner->id = id; + + if(std::find(range.cbegin(), range.cend(), owner) == range.cend()) { + owner->next = *internal::meta_context::global(); + *internal::meta_context::global() = owner; + } + + return meta_factory{&owner->prop}; + } + + /** + * @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 + auto base() ENTT_NOEXCEPT { + static_assert(!std::is_same_v && std::is_base_of_v, "Invalid base type"); + + static internal::meta_base_node node{ + nullptr, + internal::meta_node::resolve(), + [](meta_any other) ENTT_NOEXCEPT -> meta_any { + if(auto *ptr = other.data(); ptr) { + return forward_as_meta(*static_cast(static_cast(ptr))); + } + + return forward_as_meta(*static_cast(static_cast(std::as_const(other).data()))); + } + // tricks clang-format + }; + + link_base_if_required(node); + return meta_factory{}; + } + + /** + * @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() ENTT_NOEXCEPT { + static internal::meta_conv_node node{ + nullptr, + internal::meta_node>>>::resolve(), + [](const meta_any &instance) -> meta_any { + return forward_as_meta(std::invoke(Candidate, *static_cast(instance.data()))); + } + // tricks clang-format + }; + + link_conv_if_required(node); + return meta_factory{}; + } + + /** + * @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 + auto conv() ENTT_NOEXCEPT { + static internal::meta_conv_node node{ + nullptr, + internal::meta_node>>::resolve(), + [](const meta_any &instance) -> meta_any { return forward_as_meta(static_cast(*static_cast(instance.data()))); } + // tricks clang-format + }; + + link_conv_if_required(node); + return meta_factory{}; + } + + /** + * @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 An extended meta factory for the parent type. + */ + template + auto ctor() ENTT_NOEXCEPT { + using descriptor = meta_function_helper_t; + static_assert(Policy::template value, "Invalid return type for the given policy"); + static_assert(std::is_same_v>, Type>, "The function doesn't return an object of the required type"); + + static internal::meta_ctor_node node{ + nullptr, + descriptor::args_type::size, + &meta_arg, + &meta_construct + // tricks clang-format + }; + + link_ctor_if_required(node); + return meta_factory{}; + } + + /** + * @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 An extended meta factory for the parent type. + */ + template + auto ctor() ENTT_NOEXCEPT { + using descriptor = meta_function_helper_t; + + static internal::meta_ctor_node node{ + nullptr, + descriptor::args_type::size, + &meta_arg, + &meta_construct + // tricks clang-format + }; + + link_ctor_if_required(node); + return meta_factory{}; + } + + /** + * @brief Assigns a meta destructor to a meta type. + * + * Both free functions and member functions can be assigned to meta types in + * the role of destructors.
+ * The signature of a free function should be identical to the following: + * + * @code{.cpp} + * void(Type &); + * @endcode + * + * Member functions should not take arguments instead.
+ * The purpose is to give users the ability to free up resources that + * require special treatment before an object is actually destroyed. + * + * @tparam Func The actual function to use as a destructor. + * @return A meta factory for the parent type. + */ + template + auto dtor() ENTT_NOEXCEPT { + static_assert(std::is_invocable_v, "The function doesn't accept an object of the type provided"); + owner->dtor = [](void *instance) { std::invoke(Func, *static_cast(instance)); }; + return meta_factory{}; + } + + /** + * @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. + * @return An extended meta factory for the parent type. + */ + template + auto data(const id_type id) ENTT_NOEXCEPT { + if constexpr(std::is_member_object_pointer_v) { + using data_type = std::remove_reference_t>; + + static internal::meta_data_node node{ + {}, + /* this is never static */ + std::is_const_v ? internal::meta_traits::is_const : internal::meta_traits::is_none, + nullptr, + nullptr, + 1u, + internal::meta_node>::resolve(), + &meta_arg>>, + &meta_setter, + &meta_getter + // tricks clang-format + }; + + link_data_if_required(id, node); + return meta_factory, std::integral_constant>{&node.prop}; + } else { + using data_type = std::remove_reference_t>; + + static internal::meta_data_node node{ + {}, + ((std::is_same_v> || std::is_const_v) ? internal::meta_traits::is_const : internal::meta_traits::is_none) | internal::meta_traits::is_static, + nullptr, + nullptr, + 1u, + internal::meta_node>::resolve(), + &meta_arg>>, + &meta_setter, + &meta_getter + // tricks clang-format + }; + + link_data_if_required(id, node); + return meta_factory>{&node.prop}; + } + } + + /** + * @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. + * @return An extended meta factory for the parent type. + */ + template + auto data(const id_type id) ENTT_NOEXCEPT { + using data_type = std::invoke_result_t; + static_assert(Policy::template value, "Invalid return type for the given policy"); + + if constexpr(std::is_same_v) { + static internal::meta_data_node node{ + {}, + /* this is never static */ + internal::meta_traits::is_const, + nullptr, + nullptr, + 0u, + internal::meta_node>>::resolve(), + &meta_arg>, + &meta_setter, + &meta_getter + // tricks clang-format + }; + + link_data_if_required(id, node); + return meta_factory, std::integral_constant>{&node.prop}; + } else { + using args_type = typename meta_function_helper_t::args_type; + + static internal::meta_data_node node{ + {}, + /* this is never static nor const */ + internal::meta_traits::is_none, + nullptr, + nullptr, + 1u, + internal::meta_node>>::resolve(), + &meta_arg>>, + &meta_setter, + &meta_getter + // tricks clang-format + }; + + link_data_if_required(id, node); + return meta_factory, std::integral_constant>{&node.prop}; + } + } + + /** + * @brief Assigns a meta data to a meta type by means of its setters and + * getter. + * + * Multi-setter support for meta data members. All setters are tried in the + * order of definition before returning to the caller.
+ * Setters can be either free functions, member functions or a mix of them + * and are provided via a `value_list` type. + * + * @sa data + * + * @tparam Setter The actual functions to use as setters. + * @tparam Getter The actual getter function. + * @tparam Policy Optional policy (no policy set by default). + * @param id Unique identifier. + * @return An extended meta factory for the parent type. + */ + template + auto data(const id_type id) ENTT_NOEXCEPT { + return data(id, std::make_index_sequence{}); + } + + /** + * @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. + * @return An extended meta factory for the parent type. + */ + template + auto func(const id_type id) ENTT_NOEXCEPT { + using descriptor = meta_function_helper_t; + static_assert(Policy::template value, "Invalid return type for the given policy"); + + static internal::meta_func_node node{ + {}, + (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), + nullptr, + nullptr, + descriptor::args_type::size, + internal::meta_node, void, std::remove_cv_t>>>::resolve(), + &meta_arg, + &meta_invoke + // tricks clang-format + }; + + link_func_if_required(id, node); + return meta_factory>{&node.prop}; + } + +private: + internal::meta_type_node *owner; +}; + +/** + * @brief Utility function to use for reflection. + * + * This is the point from which everything starts.
+ * By invoking this function with a type that is not yet reflected, a meta type + * is created to which it will be possible to attach meta objects through a + * dedicated factory. + * + * @tparam Type Type to reflect. + * @return A meta factory for the given type. + */ +template +[[nodiscard]] auto meta() ENTT_NOEXCEPT { + auto *const node = internal::meta_node::resolve(); + // extended meta factory to allow assigning properties to opaque meta types + return meta_factory{&node->prop}; +} + +/** + * @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 list of searchable types. + * + * @param id Unique identifier. + */ +inline void meta_reset(const id_type id) ENTT_NOEXCEPT { + auto clear_chain = [](auto **curr, auto... member) { + for(; *curr; *curr = std::exchange((*curr)->next, nullptr)) { + if constexpr(sizeof...(member) != 0u) { + static_assert(sizeof...(member) == 1u, "Assert in defense of the future me"); + for(auto **sub = (&((*curr)->*member), ...); *sub; *sub = std::exchange((*sub)->next, nullptr)) {} + } + } + }; + + for(auto **it = internal::meta_context::global(); *it; it = &(*it)->next) { + if(auto *node = *it; node->id == id) { + clear_chain(&node->prop); + clear_chain(&node->base); + clear_chain(&node->conv); + clear_chain(&node->ctor); + clear_chain(&node->data, &internal::meta_data_node::prop); + clear_chain(&node->func, &internal::meta_func_node::prop); + + node->id = {}; + node->dtor = nullptr; + *it = std::exchange(node->next, nullptr); + + break; + } + } +} + +/** + * @brief Resets a type and all its parts. + * + * @sa meta_reset + * + * @tparam Type Type to reset. + */ +template +void meta_reset() ENTT_NOEXCEPT { + meta_reset(internal::meta_node::resolve()->id); +} + +/** + * @brief Resets all searchable types. + * + * @sa meta_reset + */ +inline void meta_reset() ENTT_NOEXCEPT { + while(*internal::meta_context::global()) { + meta_reset((*internal::meta_context::global())->id); + } +} + +} // namespace entt + +#endif + +// #include "meta/meta.hpp" +#ifndef ENTT_META_META_HPP +#define ENTT_META_META_HPP + +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/any.hpp" + +// #include "../core/fwd.hpp" + +// #include "../core/iterator.hpp" + +// #include "../core/type_info.hpp" + +// #include "../core/type_traits.hpp" + +// #include "../core/utility.hpp" + +// #include "adl_pointer.hpp" + +// #include "ctx.hpp" + +// #include "fwd.hpp" + +// #include "node.hpp" + +// #include "range.hpp" + +// #include "type_traits.hpp" + + +namespace entt { + +class meta_any; +class meta_type; + +/*! @brief Proxy object for sequence containers. */ +class meta_sequence_container { + class meta_iterator; + +public: + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Meta iterator type. */ + using iterator = meta_iterator; + + /*! @brief Default constructor. */ + meta_sequence_container() ENTT_NOEXCEPT = default; + + /** + * @brief Construct a proxy object for sequence containers. + * @tparam Type Type of container to wrap. + * @param instance The container to wrap. + */ + template + meta_sequence_container(std::in_place_type_t, any instance) ENTT_NOEXCEPT + : value_type_node{internal::meta_node>>::resolve()}, + size_fn{&meta_sequence_container_traits::size}, + resize_fn{&meta_sequence_container_traits::resize}, + iter_fn{&meta_sequence_container_traits::iter}, + insert_fn{&meta_sequence_container_traits::insert}, + erase_fn{&meta_sequence_container_traits::erase}, + storage{std::move(instance)} {} + + [[nodiscard]] inline meta_type value_type() const ENTT_NOEXCEPT; + [[nodiscard]] inline size_type size() const ENTT_NOEXCEPT; + inline bool resize(const size_type); + inline bool clear(); + [[nodiscard]] inline iterator begin(); + [[nodiscard]] inline iterator end(); + inline iterator insert(iterator, meta_any); + inline iterator erase(iterator); + [[nodiscard]] inline meta_any operator[](const size_type); + [[nodiscard]] inline explicit operator bool() const ENTT_NOEXCEPT; + +private: + internal::meta_type_node *value_type_node = nullptr; + size_type (*size_fn)(const any &) ENTT_NOEXCEPT = nullptr; + bool (*resize_fn)(any &, size_type) = nullptr; + iterator (*iter_fn)(any &, const bool) = nullptr; + iterator (*insert_fn)(any &, const std::ptrdiff_t, meta_any &) = nullptr; + iterator (*erase_fn)(any &, const std::ptrdiff_t) = nullptr; + any storage{}; +}; + +/*! @brief Proxy object for associative containers. */ +class meta_associative_container { + class meta_iterator; + +public: + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Meta iterator type. */ + using iterator = meta_iterator; + + /*! @brief Default constructor. */ + meta_associative_container() ENTT_NOEXCEPT = default; + + /** + * @brief Construct a proxy object for associative containers. + * @tparam Type Type of container to wrap. + * @param instance The container to wrap. + */ + template + meta_associative_container(std::in_place_type_t, any instance) ENTT_NOEXCEPT + : key_only_container{meta_associative_container_traits::key_only}, + key_type_node{internal::meta_node>>::resolve()}, + mapped_type_node{nullptr}, + value_type_node{internal::meta_node>>::resolve()}, + size_fn{&meta_associative_container_traits::size}, + clear_fn{&meta_associative_container_traits::clear}, + iter_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}, + storage{std::move(instance)} { + if constexpr(!meta_associative_container_traits::key_only) { + mapped_type_node = internal::meta_node>>::resolve(); + } + } + + [[nodiscard]] inline bool key_only() const ENTT_NOEXCEPT; + [[nodiscard]] inline meta_type key_type() const ENTT_NOEXCEPT; + [[nodiscard]] inline meta_type mapped_type() const ENTT_NOEXCEPT; + [[nodiscard]] inline meta_type value_type() const ENTT_NOEXCEPT; + [[nodiscard]] inline size_type size() const ENTT_NOEXCEPT; + inline bool clear(); + [[nodiscard]] inline iterator begin(); + [[nodiscard]] inline iterator end(); + inline bool insert(meta_any, meta_any); + inline bool erase(meta_any); + [[nodiscard]] inline iterator find(meta_any); + [[nodiscard]] inline explicit operator bool() const ENTT_NOEXCEPT; + +private: + bool key_only_container{}; + internal::meta_type_node *key_type_node = nullptr; + internal::meta_type_node *mapped_type_node = nullptr; + internal::meta_type_node *value_type_node = nullptr; + size_type (*size_fn)(const any &) ENTT_NOEXCEPT = nullptr; + bool (*clear_fn)(any &) = nullptr; + iterator (*iter_fn)(any &, const bool) = nullptr; + bool (*insert_fn)(any &, meta_any &, meta_any &) = nullptr; + bool (*erase_fn)(any &, meta_any &) = nullptr; + iterator (*find_fn)(any &, meta_any &) = nullptr; + any storage{}; +}; + +/*! @brief Opaque wrapper for values of any type. */ +class meta_any { + enum class operation : std::uint8_t { + deref, + seq, + assoc + }; + + using vtable_type = void(const operation, const any &, void *); + + template + static void basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const any &value, [[maybe_unused]] void *other) { + static_assert(std::is_same_v>, Type>, "Invalid type"); + + if constexpr(!std::is_void_v) { + switch(op) { + case operation::deref: + if constexpr(is_meta_pointer_like_v) { + if constexpr(std::is_function_v::element_type>>) { + *static_cast(other) = any_cast(value); + } else if constexpr(!std::is_same_v::element_type>, void>) { + using in_place_type = decltype(adl_meta_pointer_like::dereference(any_cast(value))); + + if constexpr(std::is_constructible_v) { + if(const auto &pointer_like = any_cast(value); pointer_like) { + static_cast(other)->emplace(adl_meta_pointer_like::dereference(pointer_like)); + } + } else { + static_cast(other)->emplace(adl_meta_pointer_like::dereference(any_cast(value))); + } + } + } + break; + case operation::seq: + if constexpr(is_complete_v>) { + *static_cast(other) = {std::in_place_type, std::move(const_cast(value))}; + } + break; + case operation::assoc: + if constexpr(is_complete_v>) { + *static_cast(other) = {std::in_place_type, std::move(const_cast(value))}; + } + break; + } + } + } + + void release() { + if(node && node->dtor && storage.owner()) { + node->dtor(storage.data()); + } + } + + meta_any(const meta_any &other, any ref) ENTT_NOEXCEPT + : storage{std::move(ref)}, + node{storage ? other.node : nullptr}, + vtable{storage ? other.vtable : &basic_vtable} {} + +public: + /*! @brief Default constructor. */ + meta_any() ENTT_NOEXCEPT + : storage{}, + node{}, + vtable{&basic_vtable} {} + + /** + * @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 meta_any(std::in_place_type_t, Args &&...args) + : storage{std::in_place_type, std::forward(args)...}, + node{internal::meta_node>>::resolve()}, + vtable{&basic_vtable>>} {} + + /** + * @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, meta_any>>> + meta_any(Type &&value) + : meta_any{std::in_place_type>>, std::forward(value)} {} + + /** + * @brief Copy constructor. + * @param other The instance to copy from. + */ + meta_any(const meta_any &other) = default; + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + meta_any(meta_any &&other) ENTT_NOEXCEPT + : storage{std::move(other.storage)}, + node{std::exchange(other.node, nullptr)}, + vtable{std::exchange(other.vtable, &basic_vtable)} {} + + /*! @brief Frees the internal storage, whatever it means. */ + ~meta_any() { + release(); + } + + /** + * @brief Copy assignment operator. + * @param other The instance to copy from. + * @return This meta any object. + */ + meta_any &operator=(const meta_any &other) { + release(); + vtable = other.vtable; + storage = other.storage; + node = other.node; + return *this; + } + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This meta any object. + */ + meta_any &operator=(meta_any &&other) ENTT_NOEXCEPT { + release(); + vtable = std::exchange(other.vtable, &basic_vtable); + storage = std::move(other.storage); + node = std::exchange(other.node, nullptr); + 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 meta any object. + */ + template + std::enable_if_t, meta_any>, meta_any &> + operator=(Type &&value) { + emplace>(std::forward(value)); + return *this; + } + + /*! @copydoc any::type */ + [[nodiscard]] inline meta_type type() const ENTT_NOEXCEPT; + + /*! @copydoc any::data */ + [[nodiscard]] const void *data() const ENTT_NOEXCEPT { + return storage.data(); + } + + /*! @copydoc any::data */ + [[nodiscard]] void *data() ENTT_NOEXCEPT { + return storage.data(); + } + + /** + * @brief Invokes the underlying function, if possible. + * + * @sa meta_func::invoke + * + * @tparam Args Types of arguments to use to invoke the function. + * @param id Unique identifier. + * @param args Parameters to use to invoke the function. + * @return A wrapper containing the returned value, if any. + */ + template + meta_any invoke(const id_type id, Args &&...args) const; + + /*! @copydoc invoke */ + template + meta_any invoke(const id_type id, Args &&...args); + + /** + * @brief Sets the value of a given variable. + * + * The type of the value is such that a cast or conversion to the type of + * the variable is possible. Otherwise, invoking the setter does nothing. + * + * @tparam Type Type of value to assign. + * @param id Unique identifier. + * @param value Parameter to use to set the underlying variable. + * @return True in case of success, false otherwise. + */ + template + bool set(const id_type id, Type &&value); + + /** + * @brief Gets the value of a given variable. + * @param id Unique identifier. + * @return A wrapper containing the value of the underlying variable. + */ + [[nodiscard]] meta_any get(const id_type id) const; + + /*! @copydoc get */ + [[nodiscard]] meta_any get(const id_type id); + + /** + * @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 { + if(const auto &info = type_id(); node && *node->info == info) { + return any_cast(&storage); + } else if(node) { + for(auto *it = node->base; it; it = it->next) { + const auto as_const = it->cast(as_ref()); + + if(const Type *base = as_const.template try_cast(); base) { + return base; + } + } + } + + return nullptr; + } + + /*! @copydoc try_cast */ + template + [[nodiscard]] Type *try_cast() { + if(const auto &info = type_id(); node && *node->info == info) { + return any_cast(&storage); + } else if(node) { + for(auto *it = node->base; it; it = it->next) { + if(Type *base = it->cast(as_ref()).template try_cast(); base) { + return base; + } + } + } + + return nullptr; + } + + /** + * @brief Tries to cast an instance to a given type. + * + * The type of the instance must be such that the cast is possible. + * + * @warning + * Attempting to perform an invalid cast results is undefined behavior. + * + * @tparam Type Type to which to cast the instance. + * @return A reference to the contained instance. + */ + template + [[nodiscard]] Type cast() const { + auto *const instance = try_cast>(); + ENTT_ASSERT(instance, "Invalid instance"); + return static_cast(*instance); + } + + /*! @copydoc cast */ + template + [[nodiscard]] Type 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 any object if there exists a viable conversion, 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 there exists a viable conversion, false otherwise. + */ + [[nodiscard]] bool allow_cast(const meta_type &type) { + if(auto other = std::as_const(*this).allow_cast(type); other) { + if(other.storage.owner()) { + std::swap(*this, other); + } + + return true; + } + + return false; + } + + /** + * @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 any object if there exists a viable conversion, an + * invalid one otherwise. + */ + template + [[nodiscard]] meta_any allow_cast() const { + const auto other = allow_cast(internal::meta_node>>::resolve()); + + if constexpr(std::is_reference_v && !std::is_const_v>) { + return other.storage.owner() ? other : meta_any{}; + } else { + return other; + } + } + + /** + * @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 there exists a viable conversion, false otherwise. + */ + template + bool allow_cast() { + if(auto other = std::as_const(*this).allow_cast(internal::meta_node>>::resolve()); other) { + if(other.storage.owner()) { + std::swap(*this, other); + return true; + } + + return (static_cast> &>(storage).data() != nullptr); + } + + return false; + } + + /*! @copydoc any::emplace */ + template + void emplace(Args &&...args) { + release(); + vtable = &basic_vtable>>; + storage.emplace(std::forward(args)...); + node = internal::meta_node>>::resolve(); + } + + /*! @copydoc any::assign */ + bool assign(const meta_any &other); + + /*! @copydoc any::assign */ + bool assign(meta_any &&other); + + /*! @copydoc any::reset */ + void reset() { + release(); + vtable = &basic_vtable; + storage.reset(); + node = nullptr; + } + + /** + * @brief Returns a sequence container proxy. + * @return A sequence container proxy for the underlying object. + */ + [[nodiscard]] meta_sequence_container as_sequence_container() ENTT_NOEXCEPT { + any detached = storage.as_ref(); + meta_sequence_container proxy; + vtable(operation::seq, detached, &proxy); + return proxy; + } + + /*! @copydoc as_sequence_container */ + [[nodiscard]] meta_sequence_container as_sequence_container() const ENTT_NOEXCEPT { + any detached = storage.as_ref(); + meta_sequence_container proxy; + vtable(operation::seq, detached, &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() ENTT_NOEXCEPT { + any detached = storage.as_ref(); + meta_associative_container proxy; + vtable(operation::assoc, detached, &proxy); + return proxy; + } + + /*! @copydoc as_associative_container */ + [[nodiscard]] meta_associative_container as_associative_container() const ENTT_NOEXCEPT { + any detached = storage.as_ref(); + meta_associative_container proxy; + vtable(operation::assoc, detached, &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*() const ENTT_NOEXCEPT { + meta_any ret{}; + vtable(operation::deref, storage, &ret); + return ret; + } + + /** + * @brief Returns false if a wrapper is invalid, true otherwise. + * @return False if the wrapper is invalid, true otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return !(node == nullptr); + } + + /*! @copydoc any::operator== */ + [[nodiscard]] bool operator==(const meta_any &other) const { + return (!node && !other.node) || (node && other.node && *node->info == *other.node->info && storage == other.storage); + } + + /*! @copydoc any::as_ref */ + [[nodiscard]] meta_any as_ref() ENTT_NOEXCEPT { + return meta_any{*this, storage.as_ref()}; + } + + /*! @copydoc any::as_ref */ + [[nodiscard]] meta_any as_ref() const ENTT_NOEXCEPT { + return meta_any{*this, storage.as_ref()}; + } + + /*! @copydoc any::owner */ + [[nodiscard]] bool owner() const ENTT_NOEXCEPT { + return storage.owner(); + } + +private: + any storage; + internal::meta_type_node *node; + vtable_type *vtable; +}; + +/** + * @brief Checks if two wrappers differ in their content. + * @param lhs A wrapper, either empty or not. + * @param rhs A wrapper, either empty or not. + * @return True if the two wrappers differ in their content, false otherwise. + */ +[[nodiscard]] inline bool operator!=(const meta_any &lhs, const meta_any &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Constructs a wrapper from a given type, passing it all arguments. + * @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. + * @return A properly initialized wrapper for an object of the given type. + */ +template +meta_any make_meta(Args &&...args) { + return meta_any{std::in_place_type, std::forward(args)...}; +} + +/** + * @brief Forwards its argument and avoids copies for lvalue references. + * @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 +meta_any forward_as_meta(Type &&value) { + return meta_any{std::in_place_type, std::decay_t, Type>>, std::forward(value)}; +} + +/** + * @brief Opaque pointers to instances of any type. + * + * A handle doesn't perform copies and isn't responsible for the contained + * object. It doesn't prolong the lifetime of the pointed instance.
+ * Handles are used to generate references to actual objects when needed. + */ +struct meta_handle { + /*! @brief Default constructor. */ + meta_handle() = default; + + /*! @brief Default copy constructor, deleted on purpose. */ + meta_handle(const meta_handle &) = delete; + + /*! @brief Default move constructor. */ + meta_handle(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 Creates a handle that points to an unmanaged object. + * @tparam Type Type of object to use to initialize the handle. + * @param value An instance of an object to use to initialize the handle. + */ + template, meta_handle>>> + meta_handle(Type &value) ENTT_NOEXCEPT + : meta_handle{} { + if constexpr(std::is_same_v, meta_any>) { + any = value.as_ref(); + } else { + any.emplace(value); + } + } + + /** + * @brief Returns false if a handle is invalid, true otherwise. + * @return False if the handle is invalid, true otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_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; + } + + /*! @copydoc operator-> */ + [[nodiscard]] const meta_any *operator->() const { + return &any; + } + +private: + meta_any any; +}; + +/*! @brief Opaque wrapper for properties of any type. */ +struct meta_prop { + /*! @brief Node type. */ + using node_type = internal::meta_prop_node; + + /** + * @brief Constructs an instance from a given node. + * @param curr The underlying node with which to construct the instance. + */ + meta_prop(const node_type *curr = nullptr) ENTT_NOEXCEPT + : node{curr} {} + + /** + * @brief Returns the stored key as a const reference. + * @return A wrapper containing the key stored with the property. + */ + [[nodiscard]] meta_any key() const { + return node->id.as_ref(); + } + + /** + * @brief Returns the stored value by copy. + * @return A wrapper containing the value stored with the property. + */ + [[nodiscard]] meta_any value() const { + return node->value; + } + + /** + * @brief Returns true if an object is valid, false otherwise. + * @return True if the object is valid, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return !(node == nullptr); + } + +private: + const node_type *node; +}; + +/*! @brief Opaque wrapper for data members. */ +struct meta_data { + /*! @brief Node type. */ + using node_type = internal::meta_data_node; + /*! @brief Unsigned integer type. */ + using size_type = typename node_type::size_type; + + /*! @copydoc meta_prop::meta_prop */ + meta_data(const node_type *curr = nullptr) ENTT_NOEXCEPT + : node{curr} {} + + /*! @copydoc meta_type::id */ + [[nodiscard]] id_type id() const ENTT_NOEXCEPT { + return node->id; + } + + /** + * @brief Returns the number of setters available. + * @return The number of setters available. + */ + [[nodiscard]] size_type arity() const ENTT_NOEXCEPT { + return node->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 ENTT_NOEXCEPT { + return !!(node->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 ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_static); + } + + /*! @copydoc meta_any::type */ + [[nodiscard]] inline meta_type type() const ENTT_NOEXCEPT; + + /** + * @brief Sets the value of a given variable. + * + * It must be possible to cast the instance to the parent type of the data + * member.
+ * The type of the value is such that a cast or conversion to the type of + * the variable is possible. Otherwise, invoking the setter does nothing. + * + * @tparam Type Type of value to assign. + * @param instance An opaque instance of the underlying type. + * @param value Parameter to use to set the underlying variable. + * @return True in case of success, false otherwise. + */ + template + bool set(meta_handle instance, Type &&value) const { + return node->set && node->set(std::move(instance), std::forward(value)); + } + + /** + * @brief Gets the value of a given variable. + * + * It must be possible to cast the instance to the parent type of the data + * member. + * + * @param instance An opaque instance of the underlying type. + * @return A wrapper containing the value of the underlying variable. + */ + [[nodiscard]] meta_any get(meta_handle instance) const { + return node->get(std::move(instance)); + } + + /** + * @brief Returns the type accepted by the i-th setter. + * @param index Index of the setter of which to return the accepted type. + * @return The type accepted by the i-th setter. + */ + [[nodiscard]] inline meta_type arg(const size_type index) const ENTT_NOEXCEPT; + + /** + * @brief Returns a range to visit registered meta properties. + * @return An iterable range to visit registered meta properties. + */ + [[nodiscard]] meta_range prop() const ENTT_NOEXCEPT { + return node->prop; + } + + /** + * @brief Lookup function for registered meta properties. + * @param key The key to use to search for a property. + * @return The registered meta property for the given key, if any. + */ + [[nodiscard]] meta_prop prop(meta_any key) const { + for(auto curr: prop()) { + if(curr.key() == key) { + return curr; + } + } + + return nullptr; + } + + /** + * @brief Returns true if an object is valid, false otherwise. + * @return True if the object is valid, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return !(node == nullptr); + } + +private: + const node_type *node; +}; + +/*! @brief Opaque wrapper for member functions. */ +struct meta_func { + /*! @brief Node type. */ + using node_type = internal::meta_func_node; + /*! @brief Unsigned integer type. */ + using size_type = typename node_type::size_type; + + /*! @copydoc meta_prop::meta_prop */ + meta_func(const node_type *curr = nullptr) ENTT_NOEXCEPT + : node{curr} {} + + /*! @copydoc meta_type::id */ + [[nodiscard]] id_type id() const ENTT_NOEXCEPT { + return node->id; + } + + /** + * @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 ENTT_NOEXCEPT { + return node->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 ENTT_NOEXCEPT { + return !!(node->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 ENTT_NOEXCEPT { + return !!(node->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 ENTT_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(const size_type index) const ENTT_NOEXCEPT; + + /** + * @brief Invokes the underlying function, if possible. + * + * To invoke a member function, the parameters must be such that a cast or + * conversion to the required types is possible. Otherwise, an empty and + * thus invalid wrapper is returned.
+ * It must be possible to cast the instance to the parent type of the member + * function. + * + * @param instance An opaque instance of the underlying type. + * @param args Parameters to use to invoke the function. + * @param sz Number of parameters to use to invoke the function. + * @return A wrapper containing the returned value, if any. + */ + meta_any invoke(meta_handle instance, meta_any *const args, const size_type sz) const { + return sz == arity() ? node->invoke(std::move(instance), args) : meta_any{}; + } + + /** + * @copybrief invoke + * + * @sa invoke + * + * @tparam Args Types of arguments to use to invoke the function. + * @param instance An opaque instance of the underlying type. + * @param args Parameters to use to invoke the function. + * @return A wrapper containing the new instance, if any. + */ + template + meta_any invoke(meta_handle instance, Args &&...args) const { + meta_any arguments[sizeof...(Args) + 1u]{std::forward(args)...}; + return invoke(std::move(instance), arguments, sizeof...(Args)); + } + + /*! @copydoc meta_data::prop */ + [[nodiscard]] meta_range prop() const ENTT_NOEXCEPT { + return node->prop; + } + + /** + * @brief Lookup function for registered meta properties. + * @param key The key to use to search for a property. + * @return The registered meta property for the given key, if any. + */ + [[nodiscard]] meta_prop prop(meta_any key) const { + for(auto curr: prop()) { + if(curr.key() == key) { + return curr; + } + } + + return nullptr; + } + + /** + * @brief Returns true if an object is valid, false otherwise. + * @return True if the object is valid, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return !(node == nullptr); + } + +private: + const node_type *node; +}; + +/*! @brief Opaque wrapper for types. */ +class meta_type { + template + [[nodiscard]] std::decay_t().*Member)> lookup(meta_any *const args, const typename internal::meta_type_node::size_type sz, Pred pred) const { + std::decay_t*Member)> candidate{}; + size_type extent{sz + 1u}; + bool ambiguous{}; + + for(auto *curr = (node->*Member); curr; curr = curr->next) { + if(pred(curr) && curr->arity == sz) { + size_type direct{}; + size_type ext{}; + + for(size_type next{}; next < sz && next == (direct + ext) && args[next]; ++next) { + const auto type = args[next].type(); + const auto other = curr->arg(next); + + if(const auto &info = other.info(); info == type.info()) { + ++direct; + } else { + ext += internal::find_by<&node_type::base>(info, type.node) + || internal::find_by<&node_type::conv>(info, type.node) + || (type.node->conversion_helper && other.node->conversion_helper); + } + } + + if((direct + ext) == sz) { + if(ext < extent) { + candidate = curr; + extent = ext; + ambiguous = false; + } else if(ext == extent) { + ambiguous = true; + } + } + } + } + + return (candidate && !ambiguous) ? candidate : decltype(candidate){}; + } + +public: + /*! @brief Node type. */ + using node_type = internal::meta_type_node; + /*! @brief Node type. */ + using base_node_type = internal::meta_base_node; + /*! @brief Unsigned integer type. */ + using size_type = typename node_type::size_type; + + /*! @copydoc meta_prop::meta_prop */ + meta_type(const node_type *curr = nullptr) ENTT_NOEXCEPT + : node{curr} {} + + /** + * @brief Constructs an instance from a given base node. + * @param curr The base node with which to construct the instance. + */ + meta_type(const base_node_type *curr) ENTT_NOEXCEPT + : node{curr ? curr->type : nullptr} {} + + /** + * @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 ENTT_NOEXCEPT { + return *node->info; + } + + /** + * @brief Returns the identifier assigned to a type. + * @return The identifier assigned to the type. + */ + [[nodiscard]] id_type id() const ENTT_NOEXCEPT { + return node->id; + } + + /** + * @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 ENTT_NOEXCEPT { + return 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 ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_arithmetic); + } + + /** + * @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 ENTT_NOEXCEPT { + return !!(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 ENTT_NOEXCEPT { + return !!(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 ENTT_NOEXCEPT { + return !!(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 ENTT_NOEXCEPT { + return !!(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 ENTT_NOEXCEPT { + return node->remove_pointer(); + } + + /** + * @brief Checks whether a type is a pointer-like type or not. + * @return True if the underlying type is a pointer-like one, false + * otherwise. + */ + [[nodiscard]] bool is_pointer_like() const ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_meta_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 ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_meta_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 ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_meta_associative_container); + } + + /** + * @brief Checks whether a type refers to a recognized class template + * specialization or not. + * @return True if the type is a recognized class template specialization, + * false otherwise. + */ + [[nodiscard]] bool is_template_specialization() const ENTT_NOEXCEPT { + return (node->templ != nullptr); + } + + /** + * @brief Returns the number of template arguments. + * @return The number of template arguments. + */ + [[nodiscard]] size_type template_arity() const ENTT_NOEXCEPT { + return node->templ ? node->templ->arity : size_type{}; + } + + /** + * @brief Returns a tag for the class template of the underlying type. + * + * @sa meta_class_template_tag + * + * @return The tag for the class template of the underlying type. + */ + [[nodiscard]] inline meta_type template_type() const ENTT_NOEXCEPT { + return node->templ ? node->templ->type : 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]] inline meta_type template_arg(const size_type index) const ENTT_NOEXCEPT { + return index < template_arity() ? node->templ->arg(index) : meta_type{}; + } + + /** + * @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 ENTT_NOEXCEPT { + return node->base; + } + + /** + * @brief Lookup function for registered base meta types. + * @param id Unique identifier. + * @return The registered base meta type for the given identifier, if any. + */ + [[nodiscard]] meta_type base(const id_type id) const { + return internal::find_by<&node_type::base>(id, node); + } + + /** + * @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 ENTT_NOEXCEPT { + return node->data; + } + + /** + * @brief Lookup function for registered meta data. + * + * Registered meta data of base classes will also be visited. + * + * @param id Unique identifier. + * @return The registered meta data for the given identifier, if any. + */ + [[nodiscard]] meta_data data(const id_type id) const { + return internal::find_by<&node_type::data>(id, node); + } + + /** + * @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 ENTT_NOEXCEPT { + return node->func; + } + + /** + * @brief Lookup function for registered meta functions. + * + * Registered meta functions of base classes will also be visited.
+ * In case of overloaded functions, the first one with the required + * identifier will be returned. + * + * @param id Unique identifier. + * @return The registered meta function for the given identifier, if any. + */ + [[nodiscard]] meta_func func(const id_type id) const { + return internal::find_by<&node_type::func>(id, node); + } + + /** + * @brief Creates an instance of the underlying type, if possible. + * + * Parameters are such that a cast or conversion to the required types is + * possible. Otherwise, an empty and thus invalid wrapper is returned.
+ * If suitable, the implicitly generated default constructor is used. + * + * @param args Parameters to use to construct the instance. + * @param sz Number of parameters to use to construct the instance. + * @return A wrapper containing the new instance, if any. + */ + [[nodiscard]] meta_any construct(meta_any *const args, const size_type sz) const { + const auto *candidate = lookup<&node_type::ctor>(args, sz, [](const auto *) { return true; }); + return candidate ? candidate->invoke(args) : ((!sz && node->default_constructor) ? node->default_constructor() : meta_any{}); + } + + /** + * @copybrief construct + * + * @sa construct + * + * @tparam Args Types of arguments to use to construct the instance. + * @param args Parameters to use to construct the instance. + * @return A wrapper containing the new instance, if any. + */ + template + [[nodiscard]] meta_any construct(Args &&...args) const { + meta_any arguments[sizeof...(Args) + 1u]{std::forward(args)...}; + return construct(arguments, sizeof...(Args)); + } + + /** + * @brief Invokes a function given an identifier, if possible. + * + * It must be possible to cast the instance to the parent type of the member + * function. + * + * @sa meta_func::invoke + * + * @param id Unique identifier. + * @param instance An opaque instance of the underlying type. + * @param args Parameters to use to invoke the function. + * @param sz Number of parameters to use to invoke the function. + * @return A wrapper containing the returned value, if any. + */ + meta_any invoke(const id_type id, meta_handle instance, meta_any *const args, const size_type sz) const { + const auto *candidate = lookup<&node_type::func>(args, sz, [id](const auto *curr) { return curr->id == id; }); + + for(auto it = base().begin(), last = base().end(); it != last && !candidate; ++it) { + candidate = it->lookup<&node_type::func>(args, sz, [id](const auto *curr) { return curr->id == id; }); + } + + return candidate ? candidate->invoke(std::move(instance), args) : meta_any{}; + } + + /** + * @copybrief invoke + * + * @sa invoke + * + * @param id Unique identifier. + * @tparam Args Types of arguments to use to invoke the function. + * @param instance An opaque instance of the underlying type. + * @param args Parameters to use to invoke the function. + * @return A wrapper containing the new instance, if any. + */ + template + meta_any invoke(const id_type id, meta_handle instance, Args &&...args) const { + meta_any arguments[sizeof...(Args) + 1u]{std::forward(args)...}; + return invoke(id, std::move(instance), arguments, sizeof...(Args)); + } + + /** + * @brief Sets the value of a given variable. + * + * It must be possible to cast the instance to the parent type of the data + * member.
+ * The type of the value is such that a cast or conversion to the type of + * the variable is possible. Otherwise, invoking the setter does nothing. + * + * @tparam Type Type of value to assign. + * @param id Unique identifier. + * @param instance An opaque instance of the underlying type. + * @param value Parameter to use to set the underlying variable. + * @return True in case of success, false otherwise. + */ + template + bool set(const id_type id, meta_handle instance, Type &&value) const { + const auto candidate = data(id); + return candidate && candidate.set(std::move(instance), std::forward(value)); + } + + /** + * @brief Gets the value of a given variable. + * + * It must be possible to cast the instance to the parent type of the data + * member. + * + * @param id Unique identifier. + * @param instance An opaque instance of the underlying type. + * @return A wrapper containing the value of the underlying variable. + */ + [[nodiscard]] meta_any get(const id_type id, meta_handle instance) const { + const auto candidate = data(id); + return candidate ? candidate.get(std::move(instance)) : meta_any{}; + } + + /** + * @brief Returns a range to visit registered top-level meta properties. + * @return An iterable range to visit registered top-level meta properties. + */ + [[nodiscard]] meta_range prop() const ENTT_NOEXCEPT { + return node->prop; + } + + /** + * @brief Lookup function for meta properties. + * + * Properties of base classes are also visited. + * + * @param key The key to use to search for a property. + * @return The registered meta property for the given key, if any. + */ + [[nodiscard]] meta_prop prop(meta_any key) const { + return internal::find_by<&internal::meta_type_node::prop>(key, node); + } + + /** + * @brief Returns true if an object is valid, false otherwise. + * @return True if the object is valid, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return !(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_type &other) const ENTT_NOEXCEPT { + return (!node && !other.node) || (node && other.node && *node->info == *other.node->info); + } + +private: + const node_type *node; +}; + +/** + * @brief Checks if two objects refer to the same type. + * @param lhs An object, either valid or not. + * @param rhs An object, either valid or not. + * @return False if the objects refer to the same node, true otherwise. + */ +[[nodiscard]] inline bool operator!=(const meta_type &lhs, const meta_type &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +[[nodiscard]] inline meta_type meta_any::type() const ENTT_NOEXCEPT { + return node; +} + +template +meta_any meta_any::invoke(const id_type id, Args &&...args) const { + return type().invoke(id, *this, std::forward(args)...); +} + +template +meta_any meta_any::invoke(const id_type id, Args &&...args) { + return type().invoke(id, *this, std::forward(args)...); +} + +template +bool meta_any::set(const id_type id, Type &&value) { + return type().set(id, *this, std::forward(value)); +} + +[[nodiscard]] inline meta_any meta_any::get(const id_type id) const { + return type().get(id, *this); +} + +[[nodiscard]] inline meta_any meta_any::get(const id_type id) { + return type().get(id, *this); +} + +[[nodiscard]] inline meta_any meta_any::allow_cast(const meta_type &type) const { + if(const auto &info = type.info(); node && *node->info == info) { + return as_ref(); + } else if(node) { + for(auto *it = node->conv; it; it = it->next) { + if(*it->type->info == info) { + return it->conv(*this); + } + } + + if(node->conversion_helper && (type.is_arithmetic() || type.is_enum())) { + // exploits the fact that arithmetic types and enums are also default constructible + auto other = type.construct(); + ENTT_ASSERT(other.node->conversion_helper, "Conversion helper not found"); + const auto value = node->conversion_helper(nullptr, storage.data()); + other.node->conversion_helper(other.storage.data(), &value); + return other; + } + + for(auto *it = node->base; it; it = it->next) { + const auto as_const = it->cast(as_ref()); + + if(auto other = as_const.allow_cast(type); other) { + return other; + } + } + } + + return {}; +} + +inline bool meta_any::assign(const meta_any &other) { + auto value = other.allow_cast(node); + return value && storage.assign(std::move(value.storage)); +} + +inline bool meta_any::assign(meta_any &&other) { + if(*node->info == *other.node->info) { + return storage.assign(std::move(other.storage)); + } + + return assign(std::as_const(other)); +} + +[[nodiscard]] inline meta_type meta_data::type() const ENTT_NOEXCEPT { + return node->type; +} + +[[nodiscard]] inline meta_type meta_func::ret() const ENTT_NOEXCEPT { + return node->ret; +} + +[[nodiscard]] inline meta_type meta_data::arg(const size_type index) const ENTT_NOEXCEPT { + return index < arity() ? node->arg(index) : meta_type{}; +} + +[[nodiscard]] inline meta_type meta_func::arg(const size_type index) const ENTT_NOEXCEPT { + return index < arity() ? node->arg(index) : meta_type{}; +} + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +class meta_sequence_container::meta_iterator final { + friend class meta_sequence_container; + + using deref_fn_type = meta_any(const any &, const std::ptrdiff_t); + + template + static meta_any deref_fn(const any &value, const std::ptrdiff_t pos) { + return meta_any{std::in_place_type::reference>, any_cast(value)[pos]}; + } + +public: + using difference_type = std::ptrdiff_t; + using value_type = meta_any; + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + meta_iterator() ENTT_NOEXCEPT + : deref{}, + offset{}, + handle{} {} + + template + explicit meta_iterator(Type &cont, const difference_type init) ENTT_NOEXCEPT + : deref{&deref_fn}, + offset{init}, + handle{cont.begin()} {} + + meta_iterator &operator++() ENTT_NOEXCEPT { + return ++offset, *this; + } + + meta_iterator operator++(int value) ENTT_NOEXCEPT { + meta_iterator orig = *this; + offset += ++value; + return orig; + } + + meta_iterator &operator--() ENTT_NOEXCEPT { + return --offset, *this; + } + + meta_iterator operator--(int value) ENTT_NOEXCEPT { + meta_iterator orig = *this; + offset -= ++value; + return orig; + } + + [[nodiscard]] reference operator*() const { + return deref(handle, offset); + } + + [[nodiscard]] pointer operator->() const { + return operator*(); + } + + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return static_cast(handle); + } + + [[nodiscard]] bool operator==(const meta_iterator &other) const ENTT_NOEXCEPT { + return offset == other.offset; + } + + [[nodiscard]] bool operator!=(const meta_iterator &other) const ENTT_NOEXCEPT { + return !(*this == other); + } + +private: + deref_fn_type *deref; + difference_type offset; + any handle; +}; + +class meta_associative_container::meta_iterator final { + enum class operation : std::uint8_t { + incr, + deref + }; + + using vtable_type = void(const operation, const any &, std::pair *); + + template + static void basic_vtable(const operation op, const any &value, std::pair *other) { + switch(op) { + case operation::incr: + ++any_cast(const_cast(value)); + break; + case operation::deref: + const auto &it = any_cast(value); + if constexpr(KeyOnly) { + other->first.emplace(*it); + } else { + other->first.emplacefirst))>(it->first); + other->second.emplacesecond))>(it->second); + } + break; + } + } + +public: + using difference_type = std::ptrdiff_t; + using value_type = std::pair; + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + meta_iterator() ENTT_NOEXCEPT + : vtable{}, + handle{} {} + + template + meta_iterator(std::integral_constant, It iter) ENTT_NOEXCEPT + : vtable{&basic_vtable}, + handle{std::move(iter)} {} + + meta_iterator &operator++() ENTT_NOEXCEPT { + vtable(operation::incr, handle, nullptr); + return *this; + } + + meta_iterator operator++(int) ENTT_NOEXCEPT { + meta_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] reference operator*() const { + reference other; + vtable(operation::deref, handle, &other); + return other; + } + + [[nodiscard]] pointer operator->() const { + return operator*(); + } + + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return static_cast(handle); + } + + [[nodiscard]] bool operator==(const meta_iterator &other) const ENTT_NOEXCEPT { + return handle == other.handle; + } + + [[nodiscard]] bool operator!=(const meta_iterator &other) const ENTT_NOEXCEPT { + return !(*this == other); + } + +private: + vtable_type *vtable; + any handle; +}; + +/** + * Internal details not to be documented. + * @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 ENTT_NOEXCEPT { + return value_type_node; +} + +/** + * @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 ENTT_NOEXCEPT { + return size_fn(storage); +} + +/** + * @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 resize_fn(storage, sz); +} + +/** + * @brief Clears the content of a container. + * @return True in case of success, false otherwise. + */ +inline bool meta_sequence_container::clear() { + return resize_fn(storage, 0u); +} + +/** + * @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 iter_fn(storage, 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 iter_fn(storage, 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(iterator it, meta_any value) { + return insert_fn(storage, it.offset, value); +} + +/** + * @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(iterator it) { + return erase_fn(storage, it.offset); +} + +/** + * @brief Returns a reference to the element at a given location of a container + * (no bounds checking is performed). + * @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 ENTT_NOEXCEPT { + return static_cast(storage); +} + +/** + * @brief Returns true if a container is also key-only, false otherwise. + * @return True if the associative container is also key-only, false otherwise. + */ +[[nodiscard]] inline bool meta_associative_container::key_only() const ENTT_NOEXCEPT { + return key_only_container; +} + +/** + * @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 ENTT_NOEXCEPT { + return key_type_node; +} + +/** + * @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 ENTT_NOEXCEPT { + return mapped_type_node; +} + +/*! @copydoc meta_sequence_container::value_type */ +[[nodiscard]] inline meta_type meta_associative_container::value_type() const ENTT_NOEXCEPT { + return value_type_node; +} + +/*! @copydoc meta_sequence_container::size */ +[[nodiscard]] inline meta_associative_container::size_type meta_associative_container::size() const ENTT_NOEXCEPT { + return size_fn(storage); +} + +/*! @copydoc meta_sequence_container::clear */ +inline bool meta_associative_container::clear() { + return clear_fn(storage); +} + +/*! @copydoc meta_sequence_container::begin */ +[[nodiscard]] inline meta_associative_container::iterator meta_associative_container::begin() { + return iter_fn(storage, false); +} + +/*! @copydoc meta_sequence_container::end */ +[[nodiscard]] inline meta_associative_container::iterator meta_associative_container::end() { + return iter_fn(storage, true); +} + +/** + * @brief Inserts an element (a key/value pair) into a container. + * @param key The key of the element to insert. + * @param value The value of the element to insert. + * @return A bool denoting whether the insertion took place. + */ +inline bool meta_associative_container::insert(meta_any key, meta_any value = {}) { + return insert_fn(storage, key, value); +} + +/** + * @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 bool meta_associative_container::erase(meta_any key) { + return erase_fn(storage, key); +} + +/** + * @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 find_fn(storage, key); +} + +/** + * @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 ENTT_NOEXCEPT { + return static_cast(storage); +} + +} // namespace entt + +#endif + +// #include "meta/node.hpp" +#ifndef ENTT_META_NODE_HPP +#define ENTT_META_NODE_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/attribute.h" + +// #include "../core/enum.hpp" + +// #include "../core/fwd.hpp" + +// #include "../core/type_info.hpp" + +// #include "../core/type_traits.hpp" + +// #include "type_traits.hpp" + + +namespace entt { + +class meta_any; +class meta_type; +struct meta_handle; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +enum class meta_traits : std::uint32_t { + is_none = 0x0000, + is_const = 0x0001, + is_static = 0x0002, + is_arithmetic = 0x0004, + is_array = 0x0008, + is_enum = 0x0010, + is_class = 0x0020, + is_pointer = 0x0040, + is_meta_pointer_like = 0x0080, + is_meta_sequence_container = 0x0100, + is_meta_associative_container = 0x0200, + _entt_enum_as_bitmask +}; + +struct meta_type_node; + +struct meta_prop_node { + meta_prop_node *next; + const meta_any &id; + meta_any &value; +}; + +struct meta_base_node { + meta_base_node *next; + meta_type_node *const type; + meta_any (*const cast)(meta_any) ENTT_NOEXCEPT; +}; + +struct meta_conv_node { + meta_conv_node *next; + meta_type_node *const type; + meta_any (*const conv)(const meta_any &); +}; + +struct meta_ctor_node { + using size_type = std::size_t; + meta_ctor_node *next; + const size_type arity; + meta_type (*const arg)(const size_type) ENTT_NOEXCEPT; + meta_any (*const invoke)(meta_any *const); +}; + +struct meta_data_node { + using size_type = std::size_t; + id_type id; + const meta_traits traits; + meta_data_node *next; + meta_prop_node *prop; + const size_type arity; + meta_type_node *const type; + meta_type (*const arg)(const size_type) ENTT_NOEXCEPT; + bool (*const set)(meta_handle, meta_any); + meta_any (*const get)(meta_handle); +}; + +struct meta_func_node { + using size_type = std::size_t; + id_type id; + const meta_traits traits; + meta_func_node *next; + meta_prop_node *prop; + const size_type arity; + meta_type_node *const ret; + meta_type (*const arg)(const size_type) ENTT_NOEXCEPT; + meta_any (*const invoke)(meta_handle, meta_any *const); +}; + +struct meta_template_node { + using size_type = std::size_t; + const size_type arity; + meta_type_node *const type; + meta_type_node *(*const arg)(const size_type)ENTT_NOEXCEPT; +}; + +struct meta_type_node { + using size_type = std::size_t; + const type_info *info; + id_type id; + const meta_traits traits; + meta_type_node *next; + meta_prop_node *prop; + const size_type size_of; + meta_type_node *(*const remove_pointer)() ENTT_NOEXCEPT; + meta_any (*const default_constructor)(); + double (*const conversion_helper)(void *, const void *); + const meta_template_node *const templ; + meta_ctor_node *ctor{nullptr}; + meta_base_node *base{nullptr}; + meta_conv_node *conv{nullptr}; + meta_data_node *data{nullptr}; + meta_func_node *func{nullptr}; + void (*dtor)(void *){nullptr}; +}; + +template +meta_type_node *meta_arg_node(type_list, const std::size_t index) ENTT_NOEXCEPT; + +template +class ENTT_API meta_node { + static_assert(std::is_same_v>>, "Invalid type"); + + [[nodiscard]] static auto *meta_default_constructor() ENTT_NOEXCEPT { + if constexpr(std::is_default_constructible_v) { + return +[]() { return meta_any{std::in_place_type}; }; + } else { + return static_cast>(nullptr); + } + } + + [[nodiscard]] static auto *meta_conversion_helper() ENTT_NOEXCEPT { + if constexpr(std::is_arithmetic_v) { + return +[](void *bin, const void *value) { + return bin ? static_cast(*static_cast(bin) = static_cast(*static_cast(value))) : static_cast(*static_cast(value)); + }; + } else if constexpr(std::is_enum_v) { + return +[](void *bin, const void *value) { + return bin ? static_cast(*static_cast(bin) = static_cast(static_cast>(*static_cast(value)))) : static_cast(*static_cast(value)); + }; + } else { + return static_cast>(nullptr); + } + } + + [[nodiscard]] static meta_template_node *meta_template_info() ENTT_NOEXCEPT { + if constexpr(is_complete_v>) { + static meta_template_node node{ + meta_template_traits::args_type::size, + meta_node::class_type>::resolve(), + [](const std::size_t index) ENTT_NOEXCEPT { return meta_arg_node(typename meta_template_traits::args_type{}, index); } + // tricks clang-format + }; + + return &node; + } else { + return nullptr; + } + } + +public: + [[nodiscard]] static meta_type_node *resolve() ENTT_NOEXCEPT { + static meta_type_node node{ + &type_id(), + {}, + internal::meta_traits::is_none + | (std::is_arithmetic_v ? internal::meta_traits::is_arithmetic : internal::meta_traits::is_none) + | (std::is_array_v ? internal::meta_traits::is_array : internal::meta_traits::is_none) + | (std::is_enum_v ? internal::meta_traits::is_enum : internal::meta_traits::is_none) + | (std::is_class_v ? internal::meta_traits::is_class : internal::meta_traits::is_none) + | (std::is_pointer_v ? internal::meta_traits::is_pointer : internal::meta_traits::is_none) + | (is_meta_pointer_like_v ? internal::meta_traits::is_meta_pointer_like : internal::meta_traits::is_none) + | (is_complete_v> ? internal::meta_traits::is_meta_sequence_container : internal::meta_traits::is_none) + | (is_complete_v> ? internal::meta_traits::is_meta_associative_container : internal::meta_traits::is_none), + nullptr, + nullptr, + size_of_v, + &meta_node>>>::resolve, + meta_default_constructor(), + meta_conversion_helper(), + meta_template_info() + // tricks clang-format + }; + + return &node; + } +}; + +template +[[nodiscard]] meta_type_node *meta_arg_node(type_list, const std::size_t index) ENTT_NOEXCEPT { + meta_type_node *args[sizeof...(Args) + 1u]{nullptr, internal::meta_node>>::resolve()...}; + return args[index + 1u]; +} + +template +[[nodiscard]] static std::decay_t().*Member)> find_by(const Type &info_or_id, const internal::meta_type_node *node) ENTT_NOEXCEPT { + for(auto *curr = node->*Member; curr; curr = curr->next) { + if constexpr(std::is_same_v) { + if(*curr->type->info == info_or_id) { + return curr; + } + } else if constexpr(std::is_same_v) { + if(curr->type->id == info_or_id) { + return curr; + } + } else { + if(curr->id == info_or_id) { + return curr; + } + } + } + + for(auto *curr = node->base; curr; curr = curr->next) { + if(auto *ret = find_by(info_or_id, curr->type); ret) { + return ret; + } + } + + return nullptr; +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +} // namespace entt + +#endif + +// #include "meta/pointer.hpp" +#ifndef ENTT_META_POINTER_HPP +#define ENTT_META_POINTER_HPP + +#include +#include +// #include "type_traits.hpp" + + +namespace entt { + +/** + * @brief Makes plain pointers pointer-like types for the meta system. + * @tparam Type Element type. + */ +template +struct is_meta_pointer_like + : std::true_type {}; + +/** + * @brief Partial specialization used to reject pointers to arrays. + * @tparam Type Type of elements of the array. + * @tparam N Number of elements of the array. + */ +template +struct is_meta_pointer_like + : std::false_type {}; + +/** + * @brief Makes `std::shared_ptr`s of any type pointer-like types for the meta + * system. + * @tparam Type Element type. + */ +template +struct is_meta_pointer_like> + : std::true_type {}; + +/** + * @brief Makes `std::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> + : std::true_type {}; + +} // namespace entt + +#endif + +// #include "meta/policy.hpp" +#ifndef ENTT_META_POLICY_HPP +#define ENTT_META_POLICY_HPP + +#include + +namespace entt { + +/*! @brief Empty class type used to request the _as ref_ policy. */ +struct as_ref_t { + /** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + template + static constexpr bool value = std::is_reference_v && !std::is_const_v>; + /** + * Internal details not to be documented. + * @endcond + */ +}; + +/*! @brief Empty class type used to request the _as cref_ policy. */ +struct as_cref_t { + /** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + template + static constexpr bool value = std::is_reference_v; + /** + * Internal details not to be documented. + * @endcond + */ +}; + +/*! @brief Empty class type used to request the _as-is_ policy. */ +struct as_is_t { + /** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + template + static constexpr bool value = true; + /** + * Internal details not to be documented. + * @endcond + */ +}; + +/*! @brief Empty class type used to request the _as void_ policy. */ +struct as_void_t { + /** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + template + static constexpr bool value = true; + /** + * Internal details not to be documented. + * @endcond + */ +}; + +} // namespace entt + +#endif + +// #include "meta/range.hpp" +#ifndef ENTT_META_RANGE_HPP +#define ENTT_META_RANGE_HPP + +#include +#include +// #include "../core/iterator.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct meta_range_iterator final { + using difference_type = std::ptrdiff_t; + using value_type = Type; + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + using node_type = Node; + + meta_range_iterator() ENTT_NOEXCEPT + : it{} {} + + meta_range_iterator(node_type *head) ENTT_NOEXCEPT + : it{head} {} + + meta_range_iterator &operator++() ENTT_NOEXCEPT { + return (it = it->next), *this; + } + + meta_range_iterator operator++(int) ENTT_NOEXCEPT { + meta_range_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return it; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + [[nodiscard]] bool operator==(const meta_range_iterator &other) const ENTT_NOEXCEPT { + return it == other.it; + } + + [[nodiscard]] bool operator!=(const meta_range_iterator &other) const ENTT_NOEXCEPT { + return !(*this == other); + } + +private: + node_type *it; +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Iterable range to use to iterate all types of meta objects. + * @tparam Type Type of meta objects returned. + * @tparam Node Type of meta nodes iterated. + */ +template +struct meta_range final { + /*! @brief Node type. */ + using node_type = Node; + /*! @brief Input iterator type. */ + using iterator = internal::meta_range_iterator; + /*! @brief Constant input iterator type. */ + using const_iterator = iterator; + + /*! @brief Default constructor. */ + meta_range() ENTT_NOEXCEPT = default; + + /** + * @brief Constructs a meta range from a given node. + * @param head The underlying node with which to construct the range. + */ + meta_range(node_type *head) ENTT_NOEXCEPT + : node{head} {} + + /** + * @brief Returns an iterator to the beginning. + * @return An iterator to the first meta object of the range. + */ + [[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT { + return iterator{node}; + } + + /*! @copydoc cbegin */ + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return cbegin(); + } + + /** + * @brief Returns an iterator to the end. + * @return An iterator to the element following the last meta object of the + * range. + */ + [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { + return iterator{}; + } + + /*! @copydoc cend */ + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return cend(); + } + +private: + node_type *node{nullptr}; +}; + +} // namespace entt + +#endif + +// #include "meta/resolve.hpp" +#ifndef ENTT_META_RESOLVE_HPP +#define ENTT_META_RESOLVE_HPP + +#include +// #include "../core/type_info.hpp" + +// #include "ctx.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. + * @return The meta type associated with the given type, if any. + */ +template +[[nodiscard]] meta_type resolve() ENTT_NOEXCEPT { + return internal::meta_node>>::resolve(); +} + +/** + * @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() ENTT_NOEXCEPT { + return *internal::meta_context::global(); +} + +/** + * @brief Returns the meta type associated with a given identifier, if any. + * @param id Unique identifier. + * @return The meta type associated with the given identifier, if any. + */ +[[nodiscard]] inline meta_type resolve(const id_type id) ENTT_NOEXCEPT { + for(auto &&curr: resolve()) { + if(curr.id() == id) { + return curr; + } + } + + return {}; +} + +/** + * @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) ENTT_NOEXCEPT { + for(auto &&curr: resolve()) { + if(curr.info() == info) { + return curr; + } + } + + return {}; +} + +} // namespace entt + +#endif + +// #include "meta/template.hpp" +#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 + +// #include "meta/type_traits.hpp" +#ifndef ENTT_META_TYPE_TRAITS_HPP +#define ENTT_META_TYPE_TRAITS_HPP + +#include +#include + +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` to true if a given type is a + * pointer-like type from the point of view of the meta system, false otherwise. + * @tparam Type Potentially pointer-like type. + */ +template +struct is_meta_pointer_like: std::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 + +// #include "meta/utility.hpp" +#ifndef ENTT_META_UTILITY_HPP +#define ENTT_META_UTILITY_HPP + +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/type_traits.hpp" + +// #include "meta.hpp" + +// #include "node.hpp" + +// #include "policy.hpp" + + +namespace entt { + +/*! @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 { + /*! @brief Meta function return type. */ + using return_type = Ret; + /*! @brief Meta function arguments. */ + using args_type = std::conditional_t, type_list, type_list>; + + /*! @brief True if the meta function is const, false otherwise. */ + static constexpr auto is_const = true; + /*! @brief True if the meta function is static, false otherwise. */ + static constexpr auto is_static = !std::is_base_of_v; +}; + +/** + * @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 { + /*! @brief Meta function return type. */ + using return_type = Ret; + /*! @brief Meta function arguments. */ + using args_type = std::conditional_t, type_list, type_list>; + + /*! @brief True if the meta function is const, false otherwise. */ + static constexpr auto is_const = false; + /*! @brief True if the meta function is static, false otherwise. */ + static constexpr auto is_static = !std::is_base_of_v; +}; + +/** + * @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 { + /*! @brief Meta data return type. */ + using return_type = Ret &; + /*! @brief Meta data arguments. */ + using args_type = std::conditional_t, type_list<>, type_list>; + + /*! @brief True if the meta data is const, false otherwise. */ + static constexpr auto is_const = false; + /*! @brief True if the meta data is static, false otherwise. */ + static constexpr auto is_static = !std::is_base_of_v; +}; + +/** + * @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 { + /*! @brief Meta function return type. */ + using return_type = Ret; + /*! @brief Meta function arguments. */ + using args_type = std::conditional_t>, Type>, type_list, type_list>; + + /*! @brief True if the meta function is const, false otherwise. */ + static constexpr auto is_const = std::is_base_of_v>, Type> && std::is_const_v>; + /*! @brief True if the meta function is static, false otherwise. */ + static constexpr auto is_static = !std::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 { + /*! @brief Meta function return type. */ + using return_type = Ret; + /*! @brief Meta function arguments. */ + using args_type = type_list<>; + + /*! @brief True if the meta function is const, false otherwise. */ + static constexpr auto is_const = false; + /*! @brief True if the meta function is static, false otherwise. */ + static constexpr auto is_static = true; +}; + +/** + * @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 constexpr meta_function_descriptor get_rid_of_noexcept(Ret (Class::*)(Args...) const); + + template + static constexpr meta_function_descriptor get_rid_of_noexcept(Ret (Class::*)(Args...)); + + template + static constexpr meta_function_descriptor get_rid_of_noexcept(Ret Class::*); + + template + static constexpr meta_function_descriptor get_rid_of_noexcept(Ret (*)(Args...)); + + template + static constexpr 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(std::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 = typename meta_function_helper::type; + +/** + * @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 +meta_any meta_dispatch([[maybe_unused]] Type &&value) { + if constexpr(std::is_same_v) { + return meta_any{std::in_place_type}; + } else if constexpr(std::is_same_v) { + return meta_any{std::in_place_type, std::forward(value)}; + } else if constexpr(std::is_same_v) { + static_assert(std::is_lvalue_reference_v, "Invalid type"); + return meta_any{std::in_place_type &>, std::as_const(value)}; + } else { + static_assert(std::is_same_v, "Policy not supported"); + return meta_any{std::forward(value)}; + } +} + +/** + * @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. + * @return The meta type of the i-th element of the list of arguments. + */ +template +[[nodiscard]] static meta_type meta_arg(const std::size_t index) ENTT_NOEXCEPT { + return internal::meta_arg_node(Type{}, 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 value Parameter 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 value) { + if constexpr(!std::is_same_v && !std::is_same_v) { + if constexpr(std::is_member_function_pointer_v || std::is_function_v>>) { + using descriptor = meta_function_helper_t; + using data_type = type_list_element_t; + + if(auto *const clazz = instance->try_cast(); clazz && value.allow_cast()) { + std::invoke(Data, *clazz, value.cast()); + return true; + } + } else if constexpr(std::is_member_object_pointer_v) { + using data_type = std::remove_reference_t::return_type>; + + if constexpr(!std::is_array_v && !std::is_const_v) { + if(auto *const clazz = instance->try_cast(); clazz && value.allow_cast()) { + std::invoke(Data, *clazz) = value.cast(); + return true; + } + } + } else { + using data_type = std::remove_reference_t; + + if constexpr(!std::is_array_v && !std::is_const_v) { + if(value.allow_cast()) { + *Data = value.cast(); + return true; + } + } + } + } + + return false; +} + +/** + * @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([[maybe_unused]] meta_handle instance) { + if constexpr(std::is_member_pointer_v || std::is_function_v>>) { + if constexpr(!std::is_array_v>>>) { + if constexpr(std::is_invocable_v) { + if(auto *clazz = instance->try_cast(); clazz) { + return meta_dispatch(std::invoke(Data, *clazz)); + } + } + + if constexpr(std::is_invocable_v) { + if(auto *fallback = instance->try_cast(); fallback) { + return meta_dispatch(std::invoke(Data, *fallback)); + } + } + } + + return meta_any{}; + } else if constexpr(std::is_pointer_v) { + if constexpr(std::is_array_v>) { + return meta_any{}; + } else { + return meta_dispatch(*Data); + } + } else { + return meta_dispatch(Data); + } +} + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +[[nodiscard]] meta_any meta_invoke_with_args(Candidate &&candidate, Args &&...args) { + if constexpr(std::is_same_v, void>) { + std::invoke(candidate, args...); + return meta_any{std::in_place_type}; + } else { + return meta_dispatch(std::invoke(candidate, args...)); + } +} + +template +[[nodiscard]] meta_any meta_invoke([[maybe_unused]] meta_handle instance, Candidate &&candidate, [[maybe_unused]] meta_any *args, std::index_sequence) { + using descriptor = meta_function_helper_t>; + + if constexpr(std::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(std::forward(candidate), *clazz, (args + Index)->cast>()...); + } + } else if constexpr(std::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(std::forward(candidate), *clazz, (args + Index)->cast>()...); + } + } else { + if(((args + Index)->allow_cast>() && ...)) { + return meta_invoke_with_args(std::forward(candidate), (args + Index)->cast>()...); + } + } + + return meta_any{}; +} + +template +[[nodiscard]] meta_any meta_construct(meta_any *const args, std::index_sequence) { + if(((args + Index)->allow_cast() && ...)) { + return meta_any{std::in_place_type, (args + Index)->cast()...}; + } + + return meta_any{}; +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @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([[maybe_unused]] meta_handle instance, Candidate &&candidate, [[maybe_unused]] meta_any *const args) { + return internal::meta_invoke(std::move(instance), std::forward(candidate), args, std::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(std::move(instance), Candidate, args, std::make_index_sequence>::args_type::size>{}); +} + +/** + * @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 internal::meta_construct(args, std::index_sequence_for{}); +} + +/** + * @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 args Parameters to use to _invoke_ the object. + * @param candidate The actual object to _invoke_. + * @return A meta any containing the returned value, if any. + */ +template +[[nodiscard]] meta_any meta_construct(Candidate &&candidate, meta_any *const args) { + if constexpr(meta_function_helper_t::is_static) { + return internal::meta_invoke({}, std::forward(candidate), args, std::make_index_sequence>::args_type::size>{}); + } else { + return internal::meta_invoke(*args, std::forward(candidate), args + 1u, std::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 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(Candidate, args); +} + +} // namespace entt + +#endif + +// #include "platform/android-ndk-r17.hpp" +#ifndef ENTT_PLATFORM_ANDROID_NDK_R17_HPP +#define ENTT_PLATFORM_ANDROID_NDK_R17_HPP + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +#ifdef __ANDROID__ +# include +# if __NDK_MAJOR__ == 17 + +# include +# include +# include + +namespace std { + +namespace internal { + +template +constexpr auto is_invocable(int) -> decltype(std::invoke(std::declval(), std::declval()...), std::true_type{}); + +template +constexpr std::false_type is_invocable(...); + +template +constexpr auto is_invocable_r(int) +-> std::enable_if_t(), std::declval()...)), Ret>, std::true_type>; + + +template +constexpr std::false_type is_invocable_r(...); + +} // namespace internal + +template +struct is_invocable: decltype(internal::is_invocable(0)) {}; + +template +inline constexpr bool is_invocable_v = std::is_invocable::value; + +template +struct is_invocable_r: decltype(internal::is_invocable_r(0)) {}; + +template +inline constexpr bool is_invocable_r_v = std::is_invocable_r::value; + +template +struct invoke_result { + using type = decltype(std::invoke(std::declval(), std::declval()...)); +}; + +template +using invoke_result_t = typename std::invoke_result::type; + +} // namespace std + +# endif +#endif + +/** + * Internal details not to be documented. + * @endcond + */ + +#endif + +// #include "poly/poly.hpp" +#ifndef ENTT_POLY_POLY_HPP +#define ENTT_POLY_POLY_HPP + +#include +#include +#include +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "../core/any.hpp" +#ifndef ENTT_CORE_ANY_HPP +#define ENTT_CORE_ANY_HPP + +#include +#include +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "../core/utility.hpp" +#ifndef ENTT_CORE_UTILITY_HPP +#define ENTT_CORE_UTILITY_HPP + +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + + +namespace entt { + +/*! @brief Identity function object (waiting for C++20). */ +struct identity { + /*! @brief Indicates that this is a transparent function object. */ + using is_transparent = void; + + /** + * @brief Returns its argument unchanged. + * @tparam Type Type of the argument. + * @param value The actual argument. + * @return The submitted value as-is. + */ + template + [[nodiscard]] constexpr Type &&operator()(Type &&value) const ENTT_NOEXCEPT { + return std::forward(value); + } +}; + +/** + * @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) ENTT_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) ENTT_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. + */ + y_combinator(Func recursive) + : func{std::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 + decltype(auto) operator()(Args &&...args) const { + return func(*this, std::forward(args)...); + } + + /*! @copydoc operator()() */ + template + decltype(auto) operator()(Args &&...args) { + return func(*this, std::forward(args)...); + } + +private: + Func func; +}; + +} // namespace entt + +#endif + +// #include "fwd.hpp" +#ifndef ENTT_CORE_FWD_HPP +#define ENTT_CORE_FWD_HPP + +#include +#include +// #include "../config/config.h" + + +namespace entt { + +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<>; + +} // namespace entt + +#endif + +// #include "type_info.hpp" +#ifndef ENTT_CORE_TYPE_INFO_HPP +#define ENTT_CORE_TYPE_INFO_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/attribute.h" +#ifndef ENTT_CORE_ATTRIBUTE_H +#define ENTT_CORE_ATTRIBUTE_H + +#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 + +#endif + +// #include "fwd.hpp" + +// #include "hashed_string.hpp" +#ifndef ENTT_CORE_HASHED_STRING_HPP +#define ENTT_CORE_HASHED_STRING_HPP + +#include +#include +// #include "../config/config.h" + +// #include "fwd.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct fnv1a_traits; + +template<> +struct fnv1a_traits { + using type = std::uint32_t; + static constexpr std::uint32_t offset = 2166136261; + static constexpr std::uint32_t prime = 16777619; +}; + +template<> +struct fnv1a_traits { + using type = std::uint64_t; + static constexpr std::uint64_t offset = 14695981039346656037ull; + static constexpr std::uint64_t prime = 1099511628211ull; +}; + +template +struct basic_hashed_string { + using value_type = Char; + using size_type = std::size_t; + using hash_type = id_type; + + const value_type *repr; + size_type length; + hash_type hash; +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @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 hs_traits = internal::fnv1a_traits; + + struct const_wrapper { + // non-explicit constructor on purpose + constexpr const_wrapper(const Char *str) ENTT_NOEXCEPT: repr{str} {} + const Char *repr; + }; + + // Fowler–Noll–Vo hash function v. 1a - the good + [[nodiscard]] static constexpr auto helper(const Char *str) ENTT_NOEXCEPT { + base_type base{str, 0u, hs_traits::offset}; + + for(; str[base.length]; ++base.length) { + base.hash = (base.hash ^ static_cast(str[base.length])) * hs_traits::prime; + } + + return base; + } + + // Fowler–Noll–Vo hash function v. 1a - the good + [[nodiscard]] static constexpr auto helper(const Char *str, const std::size_t len) ENTT_NOEXCEPT { + base_type base{str, len, hs_traits::offset}; + + for(size_type pos{}; pos < len; ++pos) { + base.hash = (base.hash ^ static_cast(str[pos])) * hs_traits::prime; + } + + return base; + } + +public: + /*! @brief Character type. */ + using value_type = typename base_type::value_type; + /*! @brief Unsigned integer type. */ + using size_type = typename base_type::size_type; + /*! @brief Unsigned integer type. */ + using hash_type = typename 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) ENTT_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 + [[nodiscard]] static constexpr hash_type value(const value_type (&str)[N]) ENTT_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) ENTT_NOEXCEPT { + return basic_hashed_string{wrapper}; + } + + /*! @brief Constructs an empty hashed string. */ + constexpr basic_hashed_string() ENTT_NOEXCEPT + : base_type{} {} + + /** + * @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) ENTT_NOEXCEPT + : base_type{helper(str, len)} {} + + /** + * @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 + constexpr basic_hashed_string(const value_type (&str)[N]) ENTT_NOEXCEPT + : base_type{helper(str)} {} + + /** + * @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) ENTT_NOEXCEPT + : base_type{helper(wrapper.repr)} {} + + /** + * @brief Returns the size a hashed string. + * @return The size of the hashed string. + */ + [[nodiscard]] constexpr size_type size() const ENTT_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 ENTT_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 ENTT_NOEXCEPT { + return base_type::hash; + } + + /*! @copydoc data */ + [[nodiscard]] constexpr operator const value_type *() const ENTT_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 ENTT_NOEXCEPT { + return 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, const std::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 +basic_hashed_string(const Char (&str)[N]) -> basic_hashed_string; + +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the two hashed strings are identical, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator==(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return lhs.value() == rhs.value(); +} + +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the two hashed strings differ, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator!=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is less than the second, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator<(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return lhs.value() < rhs.value(); +} + +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is less than or equal to the second, false + * otherwise. + */ +template +[[nodiscard]] constexpr bool operator<=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return !(rhs < lhs); +} + +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is greater than the second, false + * otherwise. + */ +template +[[nodiscard]] constexpr bool operator>(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is greater than or equal to the second, + * false otherwise. + */ +template +[[nodiscard]] constexpr bool operator>=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +/*! @brief Aliases for common character types. */ +using hashed_string = basic_hashed_string; + +/*! @brief Aliases for common character types. */ +using hashed_wstring = 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]] constexpr hashed_string operator"" _hs(const char *str, std::size_t) ENTT_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]] constexpr hashed_wstring operator"" _hws(const wchar_t *str, std::size_t) ENTT_NOEXCEPT { + return hashed_wstring{str}; +} + +} // namespace literals + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +struct ENTT_API type_index final { + [[nodiscard]] static id_type next() ENTT_NOEXCEPT { + static ENTT_MAYBE_ATOMIC(id_type) value{}; + return value++; + } +}; + +template +[[nodiscard]] constexpr auto stripped_type_name() ENTT_NOEXCEPT { +#if defined ENTT_PRETTY_FUNCTION + std::string_view pretty_function{ENTT_PRETTY_FUNCTION}; + auto first = pretty_function.find_first_not_of(' ', pretty_function.find_first_of(ENTT_PRETTY_FUNCTION_PREFIX) + 1); + auto value = pretty_function.substr(first, pretty_function.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first); + return value; +#else + return std::string_view{""}; +#endif +} + +template().find_first_of('.')> +[[nodiscard]] static constexpr std::string_view type_name(int) ENTT_NOEXCEPT { + constexpr auto value = stripped_type_name(); + return value; +} + +template +[[nodiscard]] static std::string_view type_name(char) ENTT_NOEXCEPT { + static const auto value = stripped_type_name(); + return value; +} + +template().find_first_of('.')> +[[nodiscard]] static constexpr id_type type_hash(int) ENTT_NOEXCEPT { + constexpr auto stripped = stripped_type_name(); + constexpr auto value = hashed_string::value(stripped.data(), stripped.size()); + return value; +} + +template +[[nodiscard]] static id_type type_hash(char) ENTT_NOEXCEPT { + static const auto value = [](const auto stripped) { + return hashed_string::value(stripped.data(), stripped.size()); + }(stripped_type_name()); + return value; +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @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() ENTT_NOEXCEPT { + static const id_type value = internal::type_index::next(); + return value; + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const ENTT_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() ENTT_NOEXCEPT { + return internal::type_hash(0); +#else + [[nodiscard]] static constexpr id_type value() ENTT_NOEXCEPT { + return type_index::value(); +#endif + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const ENTT_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 std::string_view value() ENTT_NOEXCEPT { + return internal::type_name(0); + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator std::string_view() const ENTT_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 + constexpr type_info(std::in_place_type_t) ENTT_NOEXCEPT + : seq{type_index>>::value()}, + identifier{type_hash>>::value()}, + alias{type_name>>::value()} {} + + /** + * @brief Type index. + * @return Type index. + */ + [[nodiscard]] constexpr id_type index() const ENTT_NOEXCEPT { + return seq; + } + + /** + * @brief Type hash. + * @return Type hash. + */ + [[nodiscard]] constexpr id_type hash() const ENTT_NOEXCEPT { + return identifier; + } + + /** + * @brief Type name. + * @return Type name. + */ + [[nodiscard]] constexpr std::string_view name() const ENTT_NOEXCEPT { + return alias; + } + +private: + id_type seq; + id_type identifier; + std::string_view alias; +}; + +/** + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects are identical, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator==(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return lhs.hash() == rhs.hash(); +} + +/** + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects differ, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator!=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than the second, false otherwise. + */ +[[nodiscard]] constexpr bool operator<(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return lhs.index() < rhs.index(); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than or equal to the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator<=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(rhs < lhs); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator>(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than or equal to the second, + * false otherwise. + */ +[[nodiscard]] constexpr bool operator>=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +/** + * @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() ENTT_NOEXCEPT { + if constexpr(std::is_same_v>>) { + static type_info instance{std::in_place_type}; + return instance; + } else { + return type_id>>(); + } +} + +/*! @copydoc type_id */ +template +[[nodiscard]] const type_info &type_id(Type &&) ENTT_NOEXCEPT { + return type_id>>(); +} + +} // namespace entt + +#endif + +// #include "type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP + +#include +#include +#include +#include +// #include "../config/config.h" + +// #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 TURN_OFF_DOXYGEN */ 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 Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; + +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; + +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; + +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; + +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::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 = std::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 Type 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 Type 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 = Type; +}; + +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; + +/** + * @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 +constexpr 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 = typename 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 = typename type_list_cat::type; + +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; + +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; +}; + +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A type list. + */ +template +using type_list_unique_t = typename type_list_unique::type; + +/** + * @brief Provides the member constant `value` 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>: std::disjunction...> {}; + +/** + * @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 = typename type_list_diff::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 value. */ + static constexpr auto value = Value; +}; + +/** + * @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 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 +constexpr 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 = typename 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 = typename value_list_cat::type; + +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::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>: std::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>: std::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 std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::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>: std::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` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; + +/*! @copydoc is_complete */ +template +struct is_complete>: std::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` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_iterator_category: std::false_type {}; + +template +struct has_iterator_category::iterator_category>>: std::true_type {}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : 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` 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 + : std::conjunction, std::negation>> {}; + +/** + * @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` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; + +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; + +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_tuple_size_value: std::false_type {}; + +template +struct has_tuple_size_value::value)>>: std::true_type {}; + +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} + +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} + +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; + } +} + +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); + } +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; + +/** + * @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 = std::remove_const_t; +}; + +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; + +/** + * @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 = typename constness_as::type; + +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); + + 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 = std::remove_pointer_t()))>; +}; + +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @brief A SBO friendly, type-safe container for single values of any type. + * @tparam Len Size of the storage reserved for the small buffer optimization. + * @tparam Align Optional alignment requirement. + */ +template +class basic_any { + enum class operation : std::uint8_t { + copy, + move, + transfer, + assign, + destroy, + compare, + get + }; + + enum class policy : std::uint8_t { + owner, + ref, + cref + }; + + using storage_type = std::aligned_storage_t; + using vtable_type = const void *(const operation, const basic_any &, const void *); + + template + static constexpr bool in_situ = Len && alignof(Type) <= alignof(storage_type) && sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v; + + template + static const void *basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const basic_any &value, [[maybe_unused]] const void *other) { + static_assert(!std::is_same_v && std::is_same_v>, Type>, "Invalid type"); + const Type *element = nullptr; + + if constexpr(in_situ) { + element = value.owner() ? reinterpret_cast(&value.storage) : static_cast(value.instance); + } else { + element = static_cast(value.instance); + } + + switch(op) { + case operation::copy: + if constexpr(std::is_copy_constructible_v) { + static_cast(const_cast(other))->initialize(*element); + } + break; + case operation::move: + if constexpr(in_situ) { + if(value.owner()) { + return new(&static_cast(const_cast(other))->storage) Type{std::move(*const_cast(element))}; + } + } + + return (static_cast(const_cast(other))->instance = std::exchange(const_cast(value).instance, nullptr)); + case operation::transfer: + if constexpr(std::is_move_assignable_v) { + *const_cast(element) = std::move(*static_cast(const_cast(other))); + return other; + } + [[fallthrough]]; + case operation::assign: + if constexpr(std::is_copy_assignable_v) { + *const_cast(element) = *static_cast(other); + return other; + } + break; + case operation::destroy: + if constexpr(in_situ) { + element->~Type(); + } else if constexpr(std::is_array_v) { + delete[] element; + } else { + delete element; + } + break; + case operation::compare: + if constexpr(!std::is_function_v && !std::is_array_v && is_equality_comparable_v) { + return *static_cast(element) == *static_cast(other) ? other : nullptr; + } else { + return (element == other) ? other : nullptr; + } + case operation::get: + return element; + } + + return nullptr; + } + + template + void initialize([[maybe_unused]] Args &&...args) { + if constexpr(!std::is_void_v) { + info = &type_id>>(); + vtable = basic_vtable>>; + + if constexpr(std::is_lvalue_reference_v) { + static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v && ...), "Invalid arguments"); + mode = std::is_const_v> ? policy::cref : policy::ref; + instance = (std::addressof(args), ...); + } else if constexpr(in_situ) { + if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v) { + new(&storage) Type{std::forward(args)...}; + } else { + new(&storage) Type(std::forward(args)...); + } + } else { + if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v) { + instance = new Type{std::forward(args)...}; + } else { + instance = new Type(std::forward(args)...); + } + } + } + } + + basic_any(const basic_any &other, const policy pol) ENTT_NOEXCEPT + : instance{other.data()}, + info{other.info}, + vtable{other.vtable}, + mode{pol} {} + +public: + /*! @brief Size of the internal storage. */ + static constexpr auto length = Len; + /*! @brief Alignment requirement. */ + static constexpr auto alignment = Align; + + /*! @brief Default constructor. */ + constexpr basic_any() ENTT_NOEXCEPT + : instance{}, + info{&type_id()}, + vtable{}, + mode{policy::owner} {} + + /** + * @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(std::in_place_type_t, Args &&...args) + : basic_any{} { + initialize(std::forward(args)...); + } + + /** + * @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, basic_any>>> + basic_any(Type &&value) + : basic_any{} { + initialize>(std::forward(value)); + } + + /** + * @brief Copy constructor. + * @param other The instance to copy from. + */ + basic_any(const basic_any &other) + : basic_any{} { + if(other.vtable) { + other.vtable(operation::copy, other, this); + } + } + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_any(basic_any &&other) ENTT_NOEXCEPT + : instance{}, + info{other.info}, + vtable{other.vtable}, + mode{other.mode} { + if(other.vtable) { + other.vtable(operation::move, other, this); + } + } + + /*! @brief Frees the internal storage, whatever it means. */ + ~basic_any() { + if(vtable && owner()) { + vtable(operation::destroy, *this, nullptr); + } + } + + /** + * @brief Copy assignment operator. + * @param other The instance to copy from. + * @return This any object. + */ + basic_any &operator=(const basic_any &other) { + reset(); + + if(other.vtable) { + other.vtable(operation::copy, other, this); + } + + return *this; + } + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This any object. + */ + basic_any &operator=(basic_any &&other) ENTT_NOEXCEPT { + reset(); + + if(other.vtable) { + other.vtable(operation::move, other, this); + info = other.info; + vtable = other.vtable; + 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 + std::enable_if_t, basic_any>, basic_any &> + operator=(Type &&value) { + emplace>(std::forward(value)); + return *this; + } + + /** + * @brief Returns the object type if any, `type_id()` otherwise. + * @return The object type if any, `type_id()` otherwise. + */ + [[nodiscard]] const type_info &type() const ENTT_NOEXCEPT { + return *info; + } + + /** + * @brief Returns an opaque pointer to the contained instance. + * @return An opaque pointer the contained instance, if any. + */ + [[nodiscard]] const void *data() const ENTT_NOEXCEPT { + return vtable ? vtable(operation::get, *this, nullptr) : nullptr; + } + + /** + * @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 ENTT_NOEXCEPT { + return *info == req ? data() : nullptr; + } + + /** + * @brief Returns an opaque pointer to the contained instance. + * @return An opaque pointer the contained instance, if any. + */ + [[nodiscard]] void *data() ENTT_NOEXCEPT { + return (!vtable || mode == policy::cref) ? nullptr : const_cast(vtable(operation::get, *this, nullptr)); + } + + /** + * @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) ENTT_NOEXCEPT { + return *info == req ? data() : nullptr; + } + + /** + * @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) { + reset(); + initialize(std::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 any &other) { + if(vtable && mode != policy::cref && *info == *other.info) { + return (vtable(operation::assign, *this, other.data()) != nullptr); + } + + return false; + } + + /*! @copydoc assign */ + bool assign(any &&other) { + if(vtable && mode != policy::cref && *info == *other.info) { + if(auto *val = other.data(); val) { + return (vtable(operation::transfer, *this, val) != nullptr); + } else { + return (vtable(operation::assign, *this, std::as_const(other).data()) != nullptr); + } + } + + return false; + } + + /*! @brief Destroys contained object */ + void reset() { + if(vtable && owner()) { + vtable(operation::destroy, *this, nullptr); + } + + info = &type_id(); + vtable = nullptr; + mode = policy::owner; + } + + /** + * @brief Returns false if a wrapper is empty, true otherwise. + * @return False if the wrapper is empty, true otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return vtable != nullptr; + } + + /** + * @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. + */ + bool operator==(const basic_any &other) const ENTT_NOEXCEPT { + if(vtable && *info == *other.info) { + return (vtable(operation::compare, *this, other.data()) != nullptr); + } + + return (!vtable && !other.vtable); + } + + /** + * @brief Aliasing constructor. + * @return A wrapper that shares a reference to an unmanaged object. + */ + [[nodiscard]] basic_any as_ref() ENTT_NOEXCEPT { + return basic_any{*this, (mode == policy::cref ? policy::cref : policy::ref)}; + } + + /*! @copydoc as_ref */ + [[nodiscard]] basic_any as_ref() const ENTT_NOEXCEPT { + return basic_any{*this, policy::cref}; + } + + /** + * @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 ENTT_NOEXCEPT { + return (mode == policy::owner); + } + +private: + union { + const void *instance; + storage_type storage; + }; + const type_info *info; + vtable_type *vtable; + policy mode; +}; + +/** + * @brief Checks if two wrappers differ in their content. + * @tparam Len Size of the storage reserved for the small buffer optimization. + * @tparam Align Alignment requirement. + * @param lhs A wrapper, either empty or not. + * @param rhs A wrapper, either empty or not. + * @return True if the two wrappers differ in their content, false otherwise. + */ +template +[[nodiscard]] inline bool operator!=(const basic_any &lhs, const basic_any &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Performs type-safe access to the contained object. + * @tparam Type Type to which conversion is required. + * @tparam Len Size of the storage reserved for the small buffer optimization. + * @tparam Align Alignment requirement. + * @param data Target any object. + * @return The element converted to the requested type. + */ +template +Type any_cast(const basic_any &data) ENTT_NOEXCEPT { + const auto *const instance = any_cast>(&data); + ENTT_ASSERT(instance, "Invalid instance"); + return static_cast(*instance); +} + +/*! @copydoc any_cast */ +template +Type any_cast(basic_any &data) ENTT_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 +Type any_cast(basic_any &&data) ENTT_NOEXCEPT { + if constexpr(std::is_copy_constructible_v>>) { + if(auto *const instance = any_cast>(&data); instance) { + return static_cast(std::move(*instance)); + } else { + return any_cast(data); + } + } else { + auto *const instance = any_cast>(&data); + ENTT_ASSERT(instance, "Invalid instance"); + return static_cast(std::move(*instance)); + } +} + +/*! @copydoc any_cast */ +template +const Type *any_cast(const basic_any *data) ENTT_NOEXCEPT { + const auto &info = type_id>>(); + return static_cast(data->data(info)); +} + +/*! @copydoc any_cast */ +template +Type *any_cast(basic_any *data) ENTT_NOEXCEPT { + const auto &info = type_id>>(); + // last attempt to make wrappers for const references return their values + return static_cast(static_cast, Type> *>(data)->data(info)); +} + +/** + * @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 storage 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, std::size_t Align = basic_any::alignment, typename... Args> +basic_any make_any(Args &&...args) { + return basic_any{std::in_place_type, std::forward(args)...}; +} + +/** + * @brief Forwards its argument and avoids copies for lvalue references. + * @tparam Len Size of the storage 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, std::size_t Align = basic_any::alignment, typename Type> +basic_any forward_as_any(Type &&value) { + return basic_any{std::in_place_type, std::decay_t, Type>>, std::forward(value)}; +} + +} // namespace entt + +#endif + +// #include "../core/type_info.hpp" +#ifndef ENTT_CORE_TYPE_INFO_HPP +#define ENTT_CORE_TYPE_INFO_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #include "../core/attribute.h" + +// #include "fwd.hpp" + +// #include "hashed_string.hpp" + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +struct ENTT_API type_index final { + [[nodiscard]] static id_type next() ENTT_NOEXCEPT { + static ENTT_MAYBE_ATOMIC(id_type) value{}; + return value++; + } +}; + +template +[[nodiscard]] constexpr auto stripped_type_name() ENTT_NOEXCEPT { +#if defined ENTT_PRETTY_FUNCTION + std::string_view pretty_function{ENTT_PRETTY_FUNCTION}; + auto first = pretty_function.find_first_not_of(' ', pretty_function.find_first_of(ENTT_PRETTY_FUNCTION_PREFIX) + 1); + auto value = pretty_function.substr(first, pretty_function.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first); + return value; +#else + return std::string_view{""}; +#endif +} + +template().find_first_of('.')> +[[nodiscard]] static constexpr std::string_view type_name(int) ENTT_NOEXCEPT { + constexpr auto value = stripped_type_name(); + return value; +} + +template +[[nodiscard]] static std::string_view type_name(char) ENTT_NOEXCEPT { + static const auto value = stripped_type_name(); + return value; +} + +template().find_first_of('.')> +[[nodiscard]] static constexpr id_type type_hash(int) ENTT_NOEXCEPT { + constexpr auto stripped = stripped_type_name(); + constexpr auto value = hashed_string::value(stripped.data(), stripped.size()); + return value; +} + +template +[[nodiscard]] static id_type type_hash(char) ENTT_NOEXCEPT { + static const auto value = [](const auto stripped) { + return hashed_string::value(stripped.data(), stripped.size()); + }(stripped_type_name()); + return value; +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @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() ENTT_NOEXCEPT { + static const id_type value = internal::type_index::next(); + return value; + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const ENTT_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() ENTT_NOEXCEPT { + return internal::type_hash(0); +#else + [[nodiscard]] static constexpr id_type value() ENTT_NOEXCEPT { + return type_index::value(); +#endif + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const ENTT_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 std::string_view value() ENTT_NOEXCEPT { + return internal::type_name(0); + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator std::string_view() const ENTT_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 + constexpr type_info(std::in_place_type_t) ENTT_NOEXCEPT + : seq{type_index>>::value()}, + identifier{type_hash>>::value()}, + alias{type_name>>::value()} {} + + /** + * @brief Type index. + * @return Type index. + */ + [[nodiscard]] constexpr id_type index() const ENTT_NOEXCEPT { + return seq; + } + + /** + * @brief Type hash. + * @return Type hash. + */ + [[nodiscard]] constexpr id_type hash() const ENTT_NOEXCEPT { + return identifier; + } + + /** + * @brief Type name. + * @return Type name. + */ + [[nodiscard]] constexpr std::string_view name() const ENTT_NOEXCEPT { + return alias; + } + +private: + id_type seq; + id_type identifier; + std::string_view alias; +}; + +/** + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects are identical, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator==(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return lhs.hash() == rhs.hash(); +} + +/** + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects differ, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator!=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than the second, false otherwise. + */ +[[nodiscard]] constexpr bool operator<(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return lhs.index() < rhs.index(); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than or equal to the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator<=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(rhs < lhs); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator>(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than or equal to the second, + * false otherwise. + */ +[[nodiscard]] constexpr bool operator>=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +/** + * @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() ENTT_NOEXCEPT { + if constexpr(std::is_same_v>>) { + static type_info instance{std::in_place_type}; + return instance; + } else { + return type_id>>(); + } +} + +/*! @copydoc type_id */ +template +[[nodiscard]] const type_info &type_id(Type &&) ENTT_NOEXCEPT { + return type_id>>(); +} + +} // namespace entt + +#endif + +// #include "../core/type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP + +#include +#include +#include +#include +// #include "../config/config.h" + +// #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 TURN_OFF_DOXYGEN */ 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 Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; + +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; + +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; + +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; + +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::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 = std::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 Type 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 Type 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 = Type; +}; + +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; + +/** + * @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 +constexpr 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 = typename 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 = typename type_list_cat::type; + +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; + +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; +}; + +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A type list. + */ +template +using type_list_unique_t = typename type_list_unique::type; + +/** + * @brief Provides the member constant `value` 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>: std::disjunction...> {}; + +/** + * @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 = typename type_list_diff::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 value. */ + static constexpr auto value = Value; +}; + +/** + * @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 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 +constexpr 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 = typename 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 = typename value_list_cat::type; + +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::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>: std::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>: std::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 std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::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>: std::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` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; + +/*! @copydoc is_complete */ +template +struct is_complete>: std::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` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_iterator_category: std::false_type {}; + +template +struct has_iterator_category::iterator_category>>: std::true_type {}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : 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` 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 + : std::conjunction, std::negation>> {}; + +/** + * @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` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; + +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; + +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_tuple_size_value: std::false_type {}; + +template +struct has_tuple_size_value::value)>>: std::true_type {}; + +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} + +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} + +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; + } +} + +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); + } +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; + +/** + * @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 = std::remove_const_t; +}; + +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; + +/** + * @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 = typename constness_as::type; + +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); + + 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 = std::remove_pointer_t()))>; +}; + +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; + +} // namespace entt + +#endif + +// #include "fwd.hpp" +#ifndef ENTT_POLY_FWD_HPP +#define ENTT_POLY_FWD_HPP + +#include +#include + +namespace entt { + +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 + + +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 + poly_inspector invoke(Args &&...args) const; + + /*! @copydoc invoke */ + template + 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 = typename Concept::template type; + + template + static auto vtable_entry(Ret (*)(inspector &, Args...)) -> Ret (*)(basic_any &, Args...); + + template + static auto vtable_entry(Ret (*)(const inspector &, Args...)) -> Ret (*)(const basic_any &, Args...); + + template + static auto vtable_entry(Ret (*)(Args...)) -> Ret (*)(const basic_any &, Args...); + + template + static auto vtable_entry(Ret (inspector::*)(Args...)) -> Ret (*)(basic_any &, Args...); + + template + static auto vtable_entry(Ret (inspector::*)(Args...) const) -> Ret (*)(const basic_any &, Args...); + + template + static auto make_vtable(value_list) ENTT_NOEXCEPT + -> decltype(std::make_tuple(vtable_entry(Candidate)...)); + + template + [[nodiscard]] static constexpr auto make_vtable(type_list) ENTT_NOEXCEPT { + if constexpr(sizeof...(Func) == 0u) { + return decltype(make_vtable(typename Concept::template impl{})){}; + } else if constexpr((std::is_function_v && ...)) { + return decltype(std::make_tuple(vtable_entry(std::declval())...)){}; + } + } + + template + static void fill_vtable_entry(Ret (*&entry)(Any &, Args...)) ENTT_NOEXCEPT { + if constexpr(std::is_invocable_r_v) { + entry = +[](Any &, Args... args) -> Ret { + return std::invoke(Candidate, std::forward(args)...); + }; + } else { + entry = +[](Any &instance, Args... args) -> Ret { + return static_cast(std::invoke(Candidate, any_cast &>(instance), std::forward(args)...)); + }; + } + } + + template + [[nodiscard]] static auto fill_vtable(std::index_sequence) ENTT_NOEXCEPT { + vtable_type impl{}; + (fill_vtable_entry>>(std::get(impl)), ...); + return impl; + } + + using vtable_type = decltype(make_vtable(Concept{})); + static constexpr bool is_mono_v = std::tuple_size_v == 1u; + +public: + /*! @brief Virtual table type. */ + using type = std::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() ENTT_NOEXCEPT { + static_assert(std::is_same_v>, "Type differs from its decayed form"); + static const vtable_type vtable = fill_vtable(std::make_index_sequence::size>{}); + + if constexpr(is_mono_v) { + return std::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(std::is_function_v>) { + return poly.vtable(poly.storage, std::forward(args)...); + } else { + return std::get(*poly.vtable)(poly.storage, std::forward(args)...); + } + } + + /*! @copydoc invoke */ + template + [[nodiscard]] decltype(auto) invoke(poly_base &self, Args &&...args) { + auto &poly = static_cast(self); + + if constexpr(std::is_function_v>) { + static_assert(Member == 0u, "Unknown member"); + return poly.vtable(poly.storage, std::forward(args)...); + } else { + return std::get(*poly.vtable)(poly.storage, std::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 std::forward(self).template invoke(self, std::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>> { + /*! @brief A poly base is allowed to snoop into a poly object. */ + friend struct poly_base; + +public: + /*! @brief Concept type. */ + using concept_type = typename Concept::template type>; + /*! @brief Virtual table type. */ + using vtable_type = typename poly_vtable::type; + + /*! @brief Default constructor. */ + basic_poly() ENTT_NOEXCEPT + : storage{}, + vtable{} {} + + /** + * @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(std::in_place_type_t, Args &&...args) + : storage{std::in_place_type, std::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>, basic_poly>>> + basic_poly(Type &&value) ENTT_NOEXCEPT + : basic_poly{std::in_place_type>>, std::forward(value)} {} + + /** + * @brief Returns the object type if any, `type_id()` otherwise. + * @return The object type if any, `type_id()` otherwise. + */ + [[nodiscard]] const type_info &type() const ENTT_NOEXCEPT { + return storage.type(); + } + + /** + * @brief Returns an opaque pointer to the contained instance. + * @return An opaque pointer the contained instance, if any. + */ + [[nodiscard]] const void *data() const ENTT_NOEXCEPT { + return storage.data(); + } + + /*! @copydoc data */ + [[nodiscard]] void *data() ENTT_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(std::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 ENTT_NOEXCEPT { + return static_cast(storage); + } + + /** + * @brief Returns a pointer to the underlying concept. + * @return A pointer to the underlying concept. + */ + [[nodiscard]] concept_type *operator->() ENTT_NOEXCEPT { + return this; + } + + /*! @copydoc operator-> */ + [[nodiscard]] const concept_type *operator->() const ENTT_NOEXCEPT { + return this; + } + + /** + * @brief Aliasing constructor. + * @return A poly that shares a reference to an unmanaged object. + */ + [[nodiscard]] basic_poly as_ref() ENTT_NOEXCEPT { + basic_poly ref{}; + ref.storage = storage.as_ref(); + ref.vtable = vtable; + return ref; + } + + /*! @copydoc as_ref */ + [[nodiscard]] basic_poly as_ref() const ENTT_NOEXCEPT { + basic_poly ref{}; + ref.storage = storage.as_ref(); + ref.vtable = vtable; + return ref; + } + +private: + basic_any storage; + vtable_type vtable; +}; + +} // namespace entt + +#endif + +// #include "process/process.hpp" +#ifndef ENTT_PROCESS_PROCESS_HPP +#define ENTT_PROCESS_PROCESS_HPP + +#include +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + + +namespace entt { + +/** + * @brief Base class for processes. + * + * This class stays true to the CRTP idiom. Derived classes must specify what's + * the intended type for elapsed times.
+ * A process should expose publicly the following member functions whether + * required: + * + * * @code{.cpp} + * void update(Delta, void *); + * @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 init(); + * @endcode + * + * It's invoked when the process joins the running queue of a scheduler. This + * happens as soon as it's attached to the scheduler if the process is a top + * level one, otherwise when it replaces its parent if the process is a + * continuation. + * + * * @code{.cpp} + * void succeeded(); + * @endcode + * + * It's invoked in case of success, immediately after an update and during the + * same tick. + * + * * @code{.cpp} + * void failed(); + * @endcode + * + * It's invoked in case of errors, immediately after an update and during the + * same tick. + * + * * @code{.cpp} + * void aborted(); + * @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` protected member functions and even pause or unpause the + * process itself. + * + * @sa scheduler + * + * @tparam Derived Actual type of process that extends the class template. + * @tparam Delta Type to use to provide elapsed time. + */ +template +class process { + enum class state : std::uint8_t { + uninitialized = 0, + running, + paused, + succeeded, + failed, + aborted, + finished, + rejected + }; + + template + auto next(std::integral_constant) + -> decltype(std::declval().init(), void()) { + static_cast(this)->init(); + } + + template + auto next(std::integral_constant, Delta delta, void *data) + -> decltype(std::declval().update(delta, data), void()) { + static_cast(this)->update(delta, data); + } + + template + auto next(std::integral_constant) + -> decltype(std::declval().succeeded(), void()) { + static_cast(this)->succeeded(); + } + + template + auto next(std::integral_constant) + -> decltype(std::declval().failed(), void()) { + static_cast(this)->failed(); + } + + template + auto next(std::integral_constant) + -> decltype(std::declval().aborted(), void()) { + static_cast(this)->aborted(); + } + + void next(...) const ENTT_NOEXCEPT {} + +protected: + /** + * @brief Terminates a process with success if it's still alive. + * + * The function is idempotent and it does nothing if the process isn't + * alive. + */ + void succeed() ENTT_NOEXCEPT { + if(alive()) { + current = state::succeeded; + } + } + + /** + * @brief Terminates a process with errors if it's still alive. + * + * The function is idempotent and it does nothing if the process isn't + * alive. + */ + void fail() ENTT_NOEXCEPT { + if(alive()) { + current = state::failed; + } + } + + /** + * @brief Stops a process if it's in a running state. + * + * The function is idempotent and it does nothing if the process isn't + * running. + */ + void pause() ENTT_NOEXCEPT { + if(current == state::running) { + current = state::paused; + } + } + + /** + * @brief Restarts a process if it's paused. + * + * The function is idempotent and it does nothing if the process isn't + * paused. + */ + void unpause() ENTT_NOEXCEPT { + if(current == state::paused) { + current = state::running; + } + } + +public: + /*! @brief Type used to provide elapsed time. */ + using delta_type = Delta; + + /*! @brief Default destructor. */ + virtual ~process() ENTT_NOEXCEPT { + static_assert(std::is_base_of_v, "Incorrect use of the class template"); + } + + /** + * @brief Aborts a process if it's still alive. + * + * The function is idempotent and it does nothing if the process isn't + * alive. + * + * @param immediately Requests an immediate operation. + */ + void abort(const bool immediately = false) { + if(alive()) { + current = state::aborted; + + if(immediately) { + tick({}); + } + } + } + + /** + * @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 ENTT_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 ENTT_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 ENTT_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 ENTT_NOEXCEPT { + return current == state::rejected; + } + + /** + * @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::uninitialized: + next(std::integral_constant{}); + current = state::running; + break; + case state::running: + next(std::integral_constant{}, delta, data); + break; + default: + // suppress warnings + break; + } + + // if it's dead, it must be notified and removed immediately + switch(current) { + case state::succeeded: + next(std::integral_constant{}); + current = state::finished; + break; + case state::failed: + next(std::integral_constant{}); + current = state::rejected; + break; + case state::aborted: + next(std::integral_constant{}); + current = state::rejected; + break; + default: + // suppress warnings + break; + } + } + +private: + state current{state::uninitialized}; +}; + +/** + * @brief Adaptor for lambdas and functors to turn them into processes. + * + * Lambdas and functors can't be used directly with a scheduler for they are not + * properly defined processes with managed life cycles.
+ * This class helps in filling the gap and turning lambdas and functors into + * full featured processes usable by a scheduler. + * + * The signature of the function call operator should be equivalent to the + * following: + * + * @code{.cpp} + * void(Delta delta, void *data, auto succeed, auto fail); + * @endcode + * + * Where: + * + * * `delta` is the elapsed time. + * * `data` is an opaque pointer to user data if any, `nullptr` otherwise. + * * `succeed` is a function to call when a process terminates with success. + * * `fail` is a function to call when a process terminates with errors. + * + * The signature of the function call operator of both `succeed` and `fail` + * is equivalent to the following: + * + * @code{.cpp} + * void(); + * @endcode + * + * Usually users shouldn't worry about creating adaptors. A scheduler will + * create them internally each and avery time a lambda or a functor is used as + * a process. + * + * @sa process + * @sa scheduler + * + * @tparam Func Actual type of process. + * @tparam Delta Type to use to provide elapsed time. + */ +template +struct process_adaptor: process, Delta>, private Func { + /** + * @brief Constructs a process adaptor from a lambda or a functor. + * @tparam Args Types of arguments to use to initialize the actual process. + * @param args Parameters to use to initialize the actual process. + */ + template + process_adaptor(Args &&...args) + : Func{std::forward(args)...} {} + + /** + * @brief Updates a process and its internal state if required. + * @param delta Elapsed time. + * @param data Optional data. + */ + void update(const Delta delta, void *data) { + Func::operator()( + delta, + data, + [this]() { this->succeed(); }, + [this]() { this->fail(); }); + } +}; + +} // namespace entt + +#endif + +// #include "process/scheduler.hpp" +#ifndef ENTT_PROCESS_SCHEDULER_HPP +#define ENTT_PROCESS_SCHEDULER_HPP + +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "process.hpp" +#ifndef ENTT_PROCESS_PROCESS_HPP +#define ENTT_PROCESS_PROCESS_HPP + +#include +#include +#include +// #include "../config/config.h" + + +namespace entt { + +/** + * @brief Base class for processes. + * + * This class stays true to the CRTP idiom. Derived classes must specify what's + * the intended type for elapsed times.
+ * A process should expose publicly the following member functions whether + * required: + * + * * @code{.cpp} + * void update(Delta, void *); + * @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 init(); + * @endcode + * + * It's invoked when the process joins the running queue of a scheduler. This + * happens as soon as it's attached to the scheduler if the process is a top + * level one, otherwise when it replaces its parent if the process is a + * continuation. + * + * * @code{.cpp} + * void succeeded(); + * @endcode + * + * It's invoked in case of success, immediately after an update and during the + * same tick. + * + * * @code{.cpp} + * void failed(); + * @endcode + * + * It's invoked in case of errors, immediately after an update and during the + * same tick. + * + * * @code{.cpp} + * void aborted(); + * @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` protected member functions and even pause or unpause the + * process itself. + * + * @sa scheduler + * + * @tparam Derived Actual type of process that extends the class template. + * @tparam Delta Type to use to provide elapsed time. + */ +template +class process { + enum class state : std::uint8_t { + uninitialized = 0, + running, + paused, + succeeded, + failed, + aborted, + finished, + rejected + }; + + template + auto next(std::integral_constant) + -> decltype(std::declval().init(), void()) { + static_cast(this)->init(); + } + + template + auto next(std::integral_constant, Delta delta, void *data) + -> decltype(std::declval().update(delta, data), void()) { + static_cast(this)->update(delta, data); + } + + template + auto next(std::integral_constant) + -> decltype(std::declval().succeeded(), void()) { + static_cast(this)->succeeded(); + } + + template + auto next(std::integral_constant) + -> decltype(std::declval().failed(), void()) { + static_cast(this)->failed(); + } + + template + auto next(std::integral_constant) + -> decltype(std::declval().aborted(), void()) { + static_cast(this)->aborted(); + } + + void next(...) const ENTT_NOEXCEPT {} + +protected: + /** + * @brief Terminates a process with success if it's still alive. + * + * The function is idempotent and it does nothing if the process isn't + * alive. + */ + void succeed() ENTT_NOEXCEPT { + if(alive()) { + current = state::succeeded; + } + } + + /** + * @brief Terminates a process with errors if it's still alive. + * + * The function is idempotent and it does nothing if the process isn't + * alive. + */ + void fail() ENTT_NOEXCEPT { + if(alive()) { + current = state::failed; + } + } + + /** + * @brief Stops a process if it's in a running state. + * + * The function is idempotent and it does nothing if the process isn't + * running. + */ + void pause() ENTT_NOEXCEPT { + if(current == state::running) { + current = state::paused; + } + } + + /** + * @brief Restarts a process if it's paused. + * + * The function is idempotent and it does nothing if the process isn't + * paused. + */ + void unpause() ENTT_NOEXCEPT { + if(current == state::paused) { + current = state::running; + } + } + +public: + /*! @brief Type used to provide elapsed time. */ + using delta_type = Delta; + + /*! @brief Default destructor. */ + virtual ~process() ENTT_NOEXCEPT { + static_assert(std::is_base_of_v, "Incorrect use of the class template"); + } + + /** + * @brief Aborts a process if it's still alive. + * + * The function is idempotent and it does nothing if the process isn't + * alive. + * + * @param immediately Requests an immediate operation. + */ + void abort(const bool immediately = false) { + if(alive()) { + current = state::aborted; + + if(immediately) { + tick({}); + } + } + } + + /** + * @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 ENTT_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 ENTT_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 ENTT_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 ENTT_NOEXCEPT { + return current == state::rejected; + } + + /** + * @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::uninitialized: + next(std::integral_constant{}); + current = state::running; + break; + case state::running: + next(std::integral_constant{}, delta, data); + break; + default: + // suppress warnings + break; + } + + // if it's dead, it must be notified and removed immediately + switch(current) { + case state::succeeded: + next(std::integral_constant{}); + current = state::finished; + break; + case state::failed: + next(std::integral_constant{}); + current = state::rejected; + break; + case state::aborted: + next(std::integral_constant{}); + current = state::rejected; + break; + default: + // suppress warnings + break; + } + } + +private: + state current{state::uninitialized}; +}; + +/** + * @brief Adaptor for lambdas and functors to turn them into processes. + * + * Lambdas and functors can't be used directly with a scheduler for they are not + * properly defined processes with managed life cycles.
+ * This class helps in filling the gap and turning lambdas and functors into + * full featured processes usable by a scheduler. + * + * The signature of the function call operator should be equivalent to the + * following: + * + * @code{.cpp} + * void(Delta delta, void *data, auto succeed, auto fail); + * @endcode + * + * Where: + * + * * `delta` is the elapsed time. + * * `data` is an opaque pointer to user data if any, `nullptr` otherwise. + * * `succeed` is a function to call when a process terminates with success. + * * `fail` is a function to call when a process terminates with errors. + * + * The signature of the function call operator of both `succeed` and `fail` + * is equivalent to the following: + * + * @code{.cpp} + * void(); + * @endcode + * + * Usually users shouldn't worry about creating adaptors. A scheduler will + * create them internally each and avery time a lambda or a functor is used as + * a process. + * + * @sa process + * @sa scheduler + * + * @tparam Func Actual type of process. + * @tparam Delta Type to use to provide elapsed time. + */ +template +struct process_adaptor: process, Delta>, private Func { + /** + * @brief Constructs a process adaptor from a lambda or a functor. + * @tparam Args Types of arguments to use to initialize the actual process. + * @param args Parameters to use to initialize the actual process. + */ + template + process_adaptor(Args &&...args) + : Func{std::forward(args)...} {} + + /** + * @brief Updates a process and its internal state if required. + * @param delta Elapsed time. + * @param data Optional data. + */ + void update(const Delta delta, void *data) { + Func::operator()( + delta, + data, + [this]() { this->succeed(); }, + [this]() { this->fail(); }); + } +}; + +} // namespace entt + +#endif + + +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. + * + * Example of use (pseudocode): + * + * @code{.cpp} + * scheduler.attach([](auto delta, void *, auto succeed, auto fail) { + * // code + * }).then(arguments...); + * @endcode + * + * 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. + */ +template +class scheduler { + struct process_handler { + using instance_type = std::unique_ptr; + using update_fn_type = bool(scheduler &, std::size_t, Delta, void *); + using abort_fn_type = void(scheduler &, std::size_t, bool); + using next_type = std::unique_ptr; + + instance_type instance; + update_fn_type *update; + abort_fn_type *abort; + next_type next; + }; + + struct continuation { + continuation(process_handler *ref) ENTT_NOEXCEPT + : handler{ref} {} + + template + continuation then(Args &&...args) { + static_assert(std::is_base_of_v, Proc>, "Invalid process type"); + auto proc = typename process_handler::instance_type{new Proc{std::forward(args)...}, &scheduler::deleter}; + handler->next.reset(new process_handler{std::move(proc), &scheduler::update, &scheduler::abort, nullptr}); + handler = handler->next.get(); + return *this; + } + + template + continuation then(Func &&func) { + return then, Delta>>(std::forward(func)); + } + + private: + process_handler *handler; + }; + + template + [[nodiscard]] static bool update(scheduler &owner, std::size_t pos, const Delta delta, void *data) { + auto *process = static_cast(owner.handlers[pos].instance.get()); + process->tick(delta, data); + + if(process->rejected()) { + return true; + } else if(process->finished()) { + if(auto &&handler = owner.handlers[pos]; handler.next) { + handler = std::move(*handler.next); + // forces the process to exit the uninitialized state + return handler.update(owner, pos, {}, nullptr); + } + + return true; + } + + return false; + } + + template + static void abort(scheduler &owner, std::size_t pos, const bool immediately) { + static_cast(owner.handlers[pos].instance.get())->abort(immediately); + } + + template + static void deleter(void *proc) { + delete static_cast(proc); + } + +public: + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + + /*! @brief Default constructor. */ + scheduler() = default; + + /*! @brief Default move constructor. */ + scheduler(scheduler &&) = default; + + /*! @brief Default move assignment operator. @return This scheduler. */ + scheduler &operator=(scheduler &&) = default; + + /** + * @brief Number of processes currently scheduled. + * @return Number of processes currently scheduled. + */ + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return handlers.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 ENTT_NOEXCEPT { + return handlers.empty(); + } + + /** + * @brief Discards all scheduled processes. + * + * Processes aren't aborted. They are discarded along with their children + * and never executed again. + */ + void clear() { + handlers.clear(); + } + + /** + * @brief Schedules a process for the next tick. + * + * Returned value is an opaque object that can be used to attach a child to + * the given process. The child is automatically scheduled when the process + * terminates and only if the process returns with success. + * + * Example of use (pseudocode): + * + * @code{.cpp} + * // schedules a task in the form of a process class + * scheduler.attach(arguments...) + * // appends a child in the form of a lambda function + * .then([](auto delta, void *, auto succeed, auto fail) { + * // code + * }) + * // appends a child in the form of another process class + * .then(); + * @endcode + * + * @tparam Proc Type of process to schedule. + * @tparam Args Types of arguments to use to initialize the process. + * @param args Parameters to use to initialize the process. + * @return An opaque object to use to concatenate processes. + */ + template + auto attach(Args &&...args) { + static_assert(std::is_base_of_v, Proc>, "Invalid process type"); + auto proc = typename process_handler::instance_type{new Proc{std::forward(args)...}, &scheduler::deleter}; + auto &&ref = handlers.emplace_back(process_handler{std::move(proc), &scheduler::update, &scheduler::abort, nullptr}); + // forces the process to exit the uninitialized state + ref.update(*this, handlers.size() - 1u, {}, nullptr); + return continuation{&handlers.back()}; + } + + /** + * @brief Schedules a process for the next tick. + * + * A process can be either a lambda or a functor. The scheduler wraps both + * of them in a process adaptor internally.
+ * The signature of the function call operator should be equivalent to the + * following: + * + * @code{.cpp} + * void(Delta delta, void *data, auto succeed, auto fail); + * @endcode + * + * Where: + * + * * `delta` is the elapsed time. + * * `data` is an opaque pointer to user data if any, `nullptr` otherwise. + * * `succeed` is a function to call when a process terminates with success. + * * `fail` is a function to call when a process terminates with errors. + * + * The signature of the function call operator of both `succeed` and `fail` + * is equivalent to the following: + * + * @code{.cpp} + * void(); + * @endcode + * + * Returned value is an opaque object that can be used to attach a child to + * the given process. The child is automatically scheduled when the process + * terminates and only if the process returns with success. + * + * Example of use (pseudocode): + * + * @code{.cpp} + * // schedules a task in the form of a lambda function + * scheduler.attach([](auto delta, void *, auto succeed, auto fail) { + * // code + * }) + * // appends a child in the form of another lambda function + * .then([](auto delta, void *, auto succeed, auto fail) { + * // code + * }) + * // appends a child in the form of a process class + * .then(arguments...); * @endcode * - * Prefer this function anyway because it has slightly better performance. + * @sa process_adaptor + * + * @tparam Func Type of process to schedule. + * @param func Either a lambda or a functor to use as a process. + * @return An opaque object to use to concatenate processes. + */ + template + auto attach(Func &&func) { + using Proc = process_adaptor, Delta>; + return attach(std::forward(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 delta, void *data = nullptr) { + for(auto pos = handlers.size(); pos; --pos) { + const auto curr = pos - 1u; + + if(const auto dead = handlers[curr].update(*this, curr, delta, data); dead) { + std::swap(handlers[curr], handlers.back()); + handlers.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 immediately Requests an immediate operation. + */ + void abort(const bool immediately = false) { + for(auto pos = handlers.size(); pos; --pos) { + const auto curr = pos - 1u; + handlers[curr].abort(*this, curr, immediately); + } + } + +private: + std::vector handlers{}; +}; + +} // namespace entt + +#endif + +// #include "resource/cache.hpp" +#ifndef ENTT_RESOURCE_RESOURCE_CACHE_HPP +#define ENTT_RESOURCE_RESOURCE_CACHE_HPP + +#include +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "../container/dense_map.hpp" +#ifndef ENTT_CONTAINER_DENSE_MAP_HPP +#define ENTT_CONTAINER_DENSE_MAP_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "../core/compressed_pair.hpp" +#ifndef ENTT_CORE_COMPRESSED_PAIR_HPP +#define ENTT_CORE_COMPRESSED_PAIR_HPP + +#include +#include +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP + +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "fwd.hpp" +#ifndef ENTT_CORE_FWD_HPP +#define ENTT_CORE_FWD_HPP + +#include +#include +// #include "../config/config.h" + + +namespace entt { + +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<>; + +} // namespace entt + +#endif + + +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 TURN_OFF_DOXYGEN */ 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 Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; + +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; + +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; + +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; + +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::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 = std::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 Type 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 Type 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 = Type; +}; + +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; + +/** + * @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 +constexpr 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 = typename 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 = typename type_list_cat::type; + +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; + +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; +}; + +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A type list. + */ +template +using type_list_unique_t = typename type_list_unique::type; + +/** + * @brief Provides the member constant `value` 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>: std::disjunction...> {}; + +/** + * @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 = typename type_list_diff::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 value. */ + static constexpr auto value = Value; +}; + +/** + * @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 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 +constexpr 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 = typename 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 = typename value_list_cat::type; + +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::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>: std::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>: std::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 std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::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>: std::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` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; + +/*! @copydoc is_complete */ +template +struct is_complete>: std::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` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_iterator_category: std::false_type {}; + +template +struct has_iterator_category::iterator_category>>: std::true_type {}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : 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` 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 + : std::conjunction, std::negation>> {}; + +/** + * @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` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; + +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; + +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_tuple_size_value: std::false_type {}; + +template +struct has_tuple_size_value::value)>>: std::true_type {}; + +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} + +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} + +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; + } +} + +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); + } +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; + +/** + * @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 = std::remove_const_t; +}; + +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; + +/** + * @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 = typename constness_as::type; + +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); + + 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 = std::remove_pointer_t()))>; +}; + +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct compressed_pair_element { + using reference = Type &; + using const_reference = const Type &; + + template>> + compressed_pair_element() + : value{} {} + + template>, compressed_pair_element>>> + compressed_pair_element(Args &&args) + : value{std::forward(args)} {} + + template + compressed_pair_element(std::tuple args, std::index_sequence) + : value{std::forward(std::get(args))...} {} + + [[nodiscard]] reference get() ENTT_NOEXCEPT { + return value; + } + + [[nodiscard]] const_reference get() const ENTT_NOEXCEPT { + return value; + } + +private: + Type value; +}; + +template +struct compressed_pair_element>>: Type { + using reference = Type &; + using const_reference = const Type &; + using base_type = Type; + + template>> + compressed_pair_element() + : base_type{} {} + + template>, compressed_pair_element>>> + compressed_pair_element(Args &&args) + : base_type{std::forward(args)} {} + + template + compressed_pair_element(std::tuple args, std::index_sequence) + : base_type{std::forward(std::get(args))...} {} + + [[nodiscard]] reference get() ENTT_NOEXCEPT { + return *this; + } + + [[nodiscard]] const_reference get() const ENTT_NOEXCEPT { + return *this; + } +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @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. + * + * @tparam Dummy Dummy template parameter used for internal purposes. + */ + template && std::is_default_constructible_v>> + constexpr compressed_pair() + : 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) = 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) + : first_base{std::forward(arg)}, + second_base{std::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(std::piecewise_construct_t, std::tuple args, std::tuple other) + : first_base{std::move(args), std::index_sequence_for{}}, + second_base{std::move(other), std::index_sequence_for{}} {} + + /** + * @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) = default; + + /** + * @brief Returns the first element that a pair stores. + * @return The first element that a pair stores. + */ + [[nodiscard]] first_type &first() ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /*! @copydoc first */ + [[nodiscard]] const first_type &first() const ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /** + * @brief Returns the second element that a pair stores. + * @return The second element that a pair stores. + */ + [[nodiscard]] second_type &second() ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /*! @copydoc second */ + [[nodiscard]] const second_type &second() const ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /** + * @brief Swaps two compressed pair objects. + * @param other The compressed pair to swap with. + */ + void swap(compressed_pair &other) { + using std::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 + decltype(auto) get() ENTT_NOEXCEPT { + if constexpr(Index == 0u) { + return first(); + } else { + static_assert(Index == 1u, "Index out of bounds"); + return second(); + } + } + + /*! @copydoc get */ + template + decltype(auto) get() const ENTT_NOEXCEPT { + if constexpr(Index == 0u) { + return first(); + } else { + static_assert(Index == 1u, "Index out of bounds"); + 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, std::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 +inline void swap(compressed_pair &lhs, compressed_pair &rhs) { + lhs.swap(rhs); +} + +} // namespace entt + +// disable structured binding support for clang 6, it messes when specializing tuple_size +#if !defined __clang_major__ || __clang_major__ > 6 +namespace std { + +/** + * @brief `std::tuple_size` specialization for `compressed_pair`s. + * @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 +struct tuple_size>: integral_constant {}; + +/** + * @brief `std::tuple_element` specialization for `compressed_pair`s. + * @tparam Index The index of the type to return. + * @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 +struct tuple_element>: conditional { + static_assert(Index < 2u, "Index out of bounds"); +}; + +} // namespace std +#endif + +#endif + +// #include "../core/iterator.hpp" +#ifndef ENTT_CORE_ITERATOR_HPP +#define ENTT_CORE_ITERATOR_HPP + +#include +#include +#include +// #include "../config/config.h" + + +namespace entt { + +/** + * @brief Helper type to use as pointer with input iterators. + * @tparam Type of wrapped value. + */ +template +struct input_iterator_pointer final { + /*! @brief Pointer type. */ + using pointer = Type *; + + /*! @brief Default copy constructor, deleted on purpose. */ + input_iterator_pointer(const input_iterator_pointer &) = delete; + + /*! @brief Default move constructor. */ + input_iterator_pointer(input_iterator_pointer &&) = default; + + /** + * @brief Constructs a proxy object by move. + * @param val Value to use to initialize the proxy object. + */ + input_iterator_pointer(Type &&val) + : value{std::move(val)} {} + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This proxy object. + */ + input_iterator_pointer &operator=(const input_iterator_pointer &) = delete; + + /** + * @brief Default move assignment operator. + * @return This proxy object. + */ + input_iterator_pointer &operator=(input_iterator_pointer &&) = default; + + /** + * @brief Access operator for accessing wrapped values. + * @return A pointer to the wrapped value. + */ + [[nodiscard]] pointer operator->() ENTT_NOEXCEPT { + return std::addressof(value); + } + +private: + Type value; +}; + +/** + * @brief Utility class to create an iterable object from a pair of iterators. + * @tparam It Type of iterator. + * @tparam Sentinel Type of sentinel. + */ +template +struct iterable_adaptor final { + /*! @brief Value type. */ + using value_type = typename std::iterator_traits::value_type; + /*! @brief Iterator type. */ + using iterator = It; + /*! @brief Sentinel type. */ + using sentinel = Sentinel; + + /*! @brief Default constructor. */ + iterable_adaptor() = default; + + /** + * @brief Creates an iterable object from a pair of iterators. + * @param from Begin iterator. + * @param to End iterator. + */ + iterable_adaptor(iterator from, sentinel to) + : first{from}, + last{to} {} + + /** + * @brief Returns an iterator to the beginning. + * @return An iterator to the first element of the range. + */ + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return first; + } + + /** + * @brief Returns an iterator to the end. + * @return An iterator to the element following the last element of the + * range. + */ + [[nodiscard]] sentinel end() const ENTT_NOEXCEPT { + return last; + } + + /*! @copydoc begin */ + [[nodiscard]] iterator cbegin() const ENTT_NOEXCEPT { + return begin(); + } + + /*! @copydoc end */ + [[nodiscard]] sentinel cend() const ENTT_NOEXCEPT { + return end(); + } + +private: + It first; + Sentinel last; +}; + +} // namespace entt + +#endif + +// #include "../core/memory.hpp" +#ifndef ENTT_CORE_MEMORY_HPP +#define ENTT_CORE_MEMORY_HPP + +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" + + +namespace entt { + +/** + * @brief Unwraps fancy pointers, does nothing otherwise (waiting for C++20). + * @tparam Type Pointer type. + * @param ptr Fancy or raw pointer. + * @return A raw pointer that represents the address of the original pointer. + */ +template +[[nodiscard]] constexpr auto to_address(Type &&ptr) ENTT_NOEXCEPT { + if constexpr(std::is_pointer_v>>) { + return ptr; + } else { + return to_address(std::forward(ptr).operator->()); + } +} + +/** + * @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) ENTT_NOEXCEPT { + if constexpr(std::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) ENTT_NOEXCEPT { + if constexpr(std::allocator_traits::propagate_on_container_move_assignment::value) { + lhs = std::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) ENTT_NOEXCEPT { + ENTT_ASSERT(std::allocator_traits::propagate_on_container_swap::value || lhs == rhs, "Cannot swap the containers"); + + if constexpr(std::allocator_traits::propagate_on_container_swap::value) { + using std::swap; + swap(lhs, rhs); + } +} + +/** + * @brief Checks whether a value is a power of two or not. + * @param value A value that may or may not be a power of two. + * @return True if the value is a power of two, false otherwise. + */ +[[nodiscard]] inline constexpr bool is_power_of_two(const std::size_t value) ENTT_NOEXCEPT { + return value && ((value & (value - 1)) == 0); +} + +/** + * @brief Computes the smallest power of two greater than or equal to a value. + * @param value The value to use. + * @return The smallest power of two greater than or equal to the given value. + */ +[[nodiscard]] inline constexpr std::size_t next_power_of_two(const std::size_t value) ENTT_NOEXCEPT { + ENTT_ASSERT(value < (std::size_t{1u} << (std::numeric_limits::digits - 1)), "Numeric limits exceeded"); + std::size_t curr = value - (value != 0u); + + for(int next = 1; next < std::numeric_limits::digits; next = next * 2) { + curr |= curr >> next; + } + + return ++curr; +} + +/** + * @brief Fast module utility function (powers of two only). + * @param value A value for which to calculate the modulus. + * @param mod _Modulus_, it must be a power of two. + * @return The common remainder. + */ +[[nodiscard]] inline constexpr std::size_t fast_mod(const std::size_t value, const std::size_t mod) ENTT_NOEXCEPT { + ENTT_ASSERT(is_power_of_two(mod), "Value must be a power of two"); + return value & (mod - 1u); +} + +/** + * @brief Deleter for allocator-aware unique pointers (waiting for C++20). + * @tparam Args Types of arguments to use to construct the object. + */ +template +struct allocation_deleter: private Allocator { + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Pointer type. */ + using pointer = typename std::allocator_traits::pointer; + + /** + * @brief Inherited constructors. + * @param alloc The allocator to use. + */ + allocation_deleter(const allocator_type &alloc) + : Allocator{alloc} {} + + /** + * @brief Destroys the pointed object and deallocates its memory. + * @param ptr A valid pointer to an object of the given type. + */ + void operator()(pointer ptr) { + using alloc_traits = typename std::allocator_traits; + alloc_traits::destroy(*this, to_address(ptr)); + alloc_traits::deallocate(*this, ptr, 1u); + } +}; + +/** + * @brief Allows `std::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 +auto allocate_unique(Allocator &allocator, Args &&...args) { + static_assert(!std::is_array_v, "Array types are not supported"); + + using alloc_traits = typename std::allocator_traits::template rebind_traits; + using allocator_type = typename alloc_traits::allocator_type; + + allocator_type alloc{allocator}; + auto ptr = alloc_traits::allocate(alloc, 1u); + + ENTT_TRY { + alloc_traits::construct(alloc, to_address(ptr), std::forward(args)...); + } + ENTT_CATCH { + alloc_traits::deallocate(alloc, ptr, 1u); + ENTT_THROW; + } + + return std::unique_ptr>{ptr, alloc}; +} + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct uses_allocator_construction { + template + static constexpr auto args([[maybe_unused]] const Allocator &allocator, Params &&...params) ENTT_NOEXCEPT { + if constexpr(!std::uses_allocator_v && std::is_constructible_v) { + return std::forward_as_tuple(std::forward(params)...); + } else { + static_assert(std::uses_allocator_v, "Ill-formed request"); + + if constexpr(std::is_constructible_v) { + return std::tuple(std::allocator_arg, allocator, std::forward(params)...); + } else { + static_assert(std::is_constructible_v, "Ill-formed request"); + return std::forward_as_tuple(std::forward(params)..., allocator); + } + } + } +}; + +template +struct uses_allocator_construction> { + using type = std::pair; + + template + static constexpr auto args(const Allocator &allocator, std::piecewise_construct_t, First &&first, Second &&second) ENTT_NOEXCEPT { + return std::make_tuple( + std::piecewise_construct, + std::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, std::forward(curr)...); }, std::forward(first)), + std::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, std::forward(curr)...); }, std::forward(second))); + } + + template + static constexpr auto args(const Allocator &allocator) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::tuple<>{}, std::tuple<>{}); + } + + template + static constexpr auto args(const Allocator &allocator, First &&first, Second &&second) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::forward(first)), std::forward_as_tuple(std::forward(second))); + } + + template + static constexpr auto args(const Allocator &allocator, const std::pair &value) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(value.first), std::forward_as_tuple(value.second)); + } + + template + static constexpr auto args(const Allocator &allocator, std::pair &&value) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::move(value.first)), std::forward_as_tuple(std::move(value.second))); + } +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @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 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 The arguments needed to create an object of the given type. + */ +template +constexpr auto uses_allocator_construction_args(const Allocator &allocator, Args &&...args) ENTT_NOEXCEPT { + return internal::uses_allocator_construction::args(allocator, std::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 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 newly created object of the given type. + */ +template +constexpr Type make_obj_using_allocator(const Allocator &allocator, Args &&...args) { + return std::make_from_tuple(internal::uses_allocator_construction::args(allocator, std::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 Allocator Type of allocator used to manage memory and elements. + * @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 Allocator &allocator, Args &&...args) { + return std::apply([&](auto &&...curr) { return new(value) Type(std::forward(curr)...); }, internal::uses_allocator_construction::args(allocator, std::forward(args)...)); +} + +} // namespace entt + +#endif + +// #include "../core/type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP + +#include +#include +#include +#include +// #include "../config/config.h" + +// #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 TURN_OFF_DOXYGEN */ 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 Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; + +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; + +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; + +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; + +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::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 = std::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 Type 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 Type 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 = Type; +}; + +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; + +/** + * @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 +constexpr 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 = typename 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 = typename type_list_cat::type; + +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; + +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; +}; + +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A type list. + */ +template +using type_list_unique_t = typename type_list_unique::type; + +/** + * @brief Provides the member constant `value` 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>: std::disjunction...> {}; + +/** + * @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 = typename type_list_diff::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 value. */ + static constexpr auto value = Value; +}; + +/** + * @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 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 +constexpr 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 = typename 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 = typename value_list_cat::type; + +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::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>: std::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>: std::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 std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::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>: std::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` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; + +/*! @copydoc is_complete */ +template +struct is_complete>: std::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` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_iterator_category: std::false_type {}; + +template +struct has_iterator_category::iterator_category>>: std::true_type {}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : 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` 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 + : std::conjunction, std::negation>> {}; + +/** + * @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` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; + +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; + +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_tuple_size_value: std::false_type {}; + +template +struct has_tuple_size_value::value)>>: std::true_type {}; + +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} + +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} + +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; + } +} + +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); + } +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; + +/** + * @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 = std::remove_const_t; +}; + +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; + +/** + * @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 = typename constness_as::type; + +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); + + 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 = std::remove_pointer_t()))>; +}; + +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; + +} // namespace entt + +#endif + +// #include "fwd.hpp" +#ifndef ENTT_CONTAINER_FWD_HPP +#define ENTT_CONTAINER_FWD_HPP + +#include +#include + +namespace entt { + +template< + typename Key, + typename Type, + typename = std::hash, + typename = std::equal_to, + typename = std::allocator>> +class dense_map; + +template< + typename Type, + typename = std::hash, + typename = std::equal_to, + typename = std::allocator> +class dense_set; + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct dense_map_node final { + using value_type = std::pair; + + template + dense_map_node(const std::size_t pos, Args &&...args) + : next{pos}, + element{std::forward(args)...} {} + + template + dense_map_node(std::allocator_arg_t, const Allocator &allocator, const std::size_t pos, Args &&...args) + : next{pos}, + element{entt::make_obj_using_allocator(allocator, std::forward(args)...)} {} + + template + dense_map_node(std::allocator_arg_t, const Allocator &allocator, const dense_map_node &other) + : next{other.next}, + element{entt::make_obj_using_allocator(allocator, other.element)} {} + + template + dense_map_node(std::allocator_arg_t, const Allocator &allocator, dense_map_node &&other) + : next{other.next}, + element{entt::make_obj_using_allocator(allocator, std::move(other.element))} {} + + std::size_t next; + value_type element; +}; + +template +class dense_map_iterator final { + template + friend class dense_map_iterator; + + using first_type = decltype(std::as_const(std::declval()->element.first)); + using second_type = decltype((std::declval()->element.second)); + +public: + using value_type = std::pair; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + dense_map_iterator() ENTT_NOEXCEPT + : it{} {} + + dense_map_iterator(const It iter) ENTT_NOEXCEPT + : it{iter} {} + + template && std::is_constructible_v>> + dense_map_iterator(const dense_map_iterator &other) ENTT_NOEXCEPT + : it{other.it} {} + + dense_map_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } + + dense_map_iterator operator++(int) ENTT_NOEXCEPT { + dense_map_iterator orig = *this; + return ++(*this), orig; + } + + dense_map_iterator &operator--() ENTT_NOEXCEPT { + return --it, *this; + } + + dense_map_iterator operator--(int) ENTT_NOEXCEPT { + dense_map_iterator orig = *this; + return operator--(), orig; + } + + dense_map_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + it += value; + return *this; + } + + dense_map_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + dense_map_iterator copy = *this; + return (copy += value); + } + + dense_map_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); + } + + dense_map_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); + } + + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + return {it[value].element.first, it[value].element.second}; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return {it->element.first, it->element.second}; + } + + template + friend std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator==(const dense_map_iterator &, const dense_map_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator<(const dense_map_iterator &, const dense_map_iterator &) ENTT_NOEXCEPT; + +private: + It it; +}; + +template +[[nodiscard]] std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it - rhs.it; +} + +template +[[nodiscard]] bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +[[nodiscard]] bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it < rhs.it; +} + +template +[[nodiscard]] bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +template +[[nodiscard]] bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +template +[[nodiscard]] bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +template +class dense_map_local_iterator final { + template + friend class dense_map_local_iterator; + + using first_type = decltype(std::as_const(std::declval()->element.first)); + using second_type = decltype((std::declval()->element.second)); + +public: + using value_type = std::pair; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + dense_map_local_iterator() ENTT_NOEXCEPT + : it{}, + offset{} {} + + dense_map_local_iterator(It iter, const std::size_t pos) ENTT_NOEXCEPT + : it{iter}, + offset{pos} {} + + template && std::is_constructible_v>> + dense_map_local_iterator(const dense_map_local_iterator &other) ENTT_NOEXCEPT + : it{other.it}, + offset{other.offset} {} + + dense_map_local_iterator &operator++() ENTT_NOEXCEPT { + return offset = it[offset].next, *this; + } + + dense_map_local_iterator operator++(int) ENTT_NOEXCEPT { + dense_map_local_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return {it[offset].element.first, it[offset].element.second}; + } + + [[nodiscard]] std::size_t index() const ENTT_NOEXCEPT { + return offset; + } + +private: + It it; + std::size_t offset; +}; + +template +[[nodiscard]] bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() == rhs.index(); +} + +template +[[nodiscard]] bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @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 std::size_t minimum_capacity = 8u; + + using node_type = internal::dense_map_node; + using alloc_traits = typename std::allocator_traits; + static_assert(std::is_same_v>, "Invalid value type"); + using sparse_container_type = std::vector>; + using packed_container_type = std::vector>; + + template + [[nodiscard]] std::size_t key_to_bucket(const Other &key) const ENTT_NOEXCEPT { + return fast_mod(sparse.second()(key), bucket_count()); + } + + template + [[nodiscard]] auto constrained_find(const Other &key, std::size_t bucket) { + for(auto it = begin(bucket), last = end(bucket); it != last; ++it) { + if(packed.second()(it->first, key)) { + return begin() + static_cast(it.index()); + } + } + + return end(); + } + + template + [[nodiscard]] auto constrained_find(const Other &key, std::size_t bucket) const { + for(auto it = cbegin(bucket), last = cend(bucket); it != last; ++it) { + if(packed.second()(it->first, key)) { + return cbegin() + static_cast(it.index()); + } + } + + 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 std::make_pair(it, false); + } + + packed.first().emplace_back(sparse.first()[index], std::piecewise_construct, std::forward_as_tuple(std::forward(key)), std::forward_as_tuple(std::forward(args)...)); + sparse.first()[index] = packed.first().size() - 1u; + rehash_if_required(); + + return std::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 = std::forward(value); + return std::make_pair(it, false); + } + + packed.first().emplace_back(sparse.first()[index], std::forward(key), std::forward(value)); + sparse.first()[index] = packed.first().size() - 1u; + rehash_if_required(); + + return std::make_pair(--end(), true); + } + + void move_and_pop(const std::size_t pos) { + if(const auto last = size() - 1u; pos != last) { + packed.first()[pos] = std::move(packed.first().back()); + size_type *curr = sparse.first().data() + key_to_bucket(packed.first().back().element.first); + for(; *curr != last; curr = &packed.first()[*curr].next) {} + *curr = pos; + } + + packed.first().pop_back(); + } + + void rehash_if_required() { + if(size() > (bucket_count() * max_load_factor())) { + rehash(bucket_count() * 2u); + } + } + +public: + /*! @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 = std::pair; + /*! @brief Unsigned integer type. */ + using size_type = std::size_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 Allocator type. */ + using allocator_type = Allocator; + /*! @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 bucket_count Minimal number of buckets. + * @param allocator The allocator to use. + */ + dense_map(const size_type bucket_count, const allocator_type &allocator) + : dense_map{bucket_count, hasher{}, key_equal{}, allocator} {} + + /** + * @brief Constructs an empty container with a given allocator, hash + * function and user supplied minimal number of buckets. + * @param bucket_count Minimal number of buckets. + * @param hash Hash function to use. + * @param allocator The allocator to use. + */ + dense_map(const size_type bucket_count, const hasher &hash, const allocator_type &allocator) + : dense_map{bucket_count, 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 bucket_count 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 bucket_count, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{}) + : sparse{allocator, hash}, + packed{allocator, equal}, + threshold{default_threshold} { + rehash(bucket_count); + } + + /*! @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{std::piecewise_construct, std::forward_as_tuple(other.sparse.first(), allocator), std::forward_as_tuple(other.sparse.second())}, + packed{std::piecewise_construct, std::forward_as_tuple(other.packed.first(), allocator), std::forward_as_tuple(other.packed.second())}, + threshold{other.threshold} {} + + /*! @brief Default move constructor. */ + dense_map(dense_map &&) = 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{std::piecewise_construct, std::forward_as_tuple(std::move(other.sparse.first()), allocator), std::forward_as_tuple(std::move(other.sparse.second()))}, + packed{std::piecewise_construct, std::forward_as_tuple(std::move(other.packed.first()), allocator), std::forward_as_tuple(std::move(other.packed.second()))}, + threshold{other.threshold} {} + + /** + * @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 &&) = default; + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return sparse.first().get_allocator(); + } + + /** + * @brief Returns an iterator to the beginning. + * + * The returned iterator points to the first instance of the internal array. + * 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 ENTT_NOEXCEPT { + return packed.first().begin(); + } + + /*! @copydoc cbegin */ + [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT { + return cbegin(); + } + + /*! @copydoc begin */ + [[nodiscard]] iterator begin() ENTT_NOEXCEPT { + return packed.first().begin(); + } + + /** + * @brief Returns an iterator to the end. + * + * The returned iterator points to the element following the last instance + * of the internal array. Attempting to dereference the returned iterator + * results in undefined behavior. + * + * @return An iterator to the element following the last instance of the + * internal array. + */ + [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { + return packed.first().end(); + } + + /*! @copydoc cend */ + [[nodiscard]] const_iterator end() const ENTT_NOEXCEPT { + return cend(); + } + + /*! @copydoc end */ + [[nodiscard]] iterator end() ENTT_NOEXCEPT { + return packed.first().end(); + } + + /** + * @brief Checks whether a container is empty. + * @return True if the container is empty, false otherwise. + */ + [[nodiscard]] bool empty() const ENTT_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 ENTT_NOEXCEPT { + return packed.first().size(); + } + + /*! @brief Clears the container. */ + void clear() ENTT_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. + */ + std::pair insert(const value_type &value) { + return insert_or_do_nothing(value.first, value.second); + } + + /*! @copydoc insert */ + std::pair insert(value_type &&value) { + return insert_or_do_nothing(std::move(value.first), std::move(value.second)); + } + + /** + * @copydoc insert + * @tparam Arg Type of the key-value pair to insert into the container. + */ + template + std::enable_if_t, std::pair> + insert(Arg &&value) { + return insert_or_do_nothing(std::forward(value).first, std::forward(value).second); + } + + /** + * @brief Inserts elements into the container, if their keys do not exist. + * @tparam It Type of input iterator. + * @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. + */ + template + void insert(It first, It 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 + std::pair insert_or_assign(const key_type &key, Arg &&value) { + return insert_or_overwrite(key, std::forward(value)); + } + + /*! @copydoc insert_or_assign */ + template + std::pair insert_or_assign(key_type &&key, Arg &&value) { + return insert_or_overwrite(std::move(key), std::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 + std::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(std::forward(args).first..., std::forward(args).second...); + } else if constexpr(sizeof...(Args) == 2u) { + return insert_or_do_nothing(std::forward(args)...); + } else { + auto &node = packed.first().emplace_back(packed.first().size(), std::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 std::make_pair(it, false); + } + + std::swap(node.next, sparse.first()[index]); + rehash_if_required(); + + return std::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 + std::pair try_emplace(const key_type &key, Args &&...args) { + return insert_or_do_nothing(key, std::forward(args)...); + } + + /*! @copydoc try_emplace */ + template + std::pair try_emplace(key_type &&key, Args &&...args) { + return insert_or_do_nothing(std::move(key), std::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()[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().data() + key_to_bucket(key); *curr != (std::numeric_limits::max)(); 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 Exchanges the contents with those of a given container. + * @param other Container to exchange the content with. + */ + void swap(dense_map &other) { + using std::swap; + swap(sparse, other.sparse); + swap(packed, other.packed); + swap(threshold, other.threshold); + } + + /** + * @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 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(std::move(key)).first->second; + } + + /** + * @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 + * value. + * @tparam Other Type of the key value of an element to search for. + * @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. + */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + find(const Other &key) { + return constrained_find(key, key_to_bucket(key)); + } + + /*! @copydoc find */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + find(const Other &key) const { + return constrained_find(key, key_to_bucket(key)); + } + + /** + * @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. + * @tparam Other Type of the key value of an element to search for. + * @param key Key value of an element to search for. + * @return True if there is such an element, false otherwise. + */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + contains(const Other &key) const { + 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().begin(), 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().begin(), 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 {packed.first().begin(), (std::numeric_limits::max)()}; + } + + /** + * @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([[maybe_unused]] 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 {packed.first().begin(), (std::numeric_limits::max)()}; + } + + /** + * @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(std::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 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 count New number of buckets. + */ + void rehash(const size_type count) { + auto value = (std::max)(count, minimum_capacity); + value = (std::max)(value, static_cast(size() / max_load_factor())); + + if(const auto sz = next_power_of_two(value); sz != bucket_count()) { + sparse.first().resize(sz); + std::fill(sparse.first().begin(), sparse.first().end(), (std::numeric_limits::max)()); + + for(size_type pos{}, last = size(); pos < last; ++pos) { + const auto index = key_to_bucket(packed.first()[pos].element.first); + packed.first()[pos].next = std::exchange(sparse.first()[index], pos); + } + } + } + + /** + * @brief Reserves space for at least the specified number of elements and + * regenerates the hash table. + * @param count New number of elements. + */ + void reserve(const size_type count) { + packed.first().reserve(count); + rehash(static_cast(std::ceil(count / 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; +}; + +} // namespace entt + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace std { + +template +struct uses_allocator, Allocator> + : std::true_type {}; + +} // namespace std + +/** + * Internal details not to be documented. + * @endcond + */ + +#endif + +// #include "../core/compressed_pair.hpp" +#ifndef ENTT_CORE_COMPRESSED_PAIR_HPP +#define ENTT_CORE_COMPRESSED_PAIR_HPP + +#include +#include +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP + +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "fwd.hpp" +#ifndef ENTT_CORE_FWD_HPP +#define ENTT_CORE_FWD_HPP + +#include +#include +// #include "../config/config.h" + + +namespace entt { + +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<>; + +} // namespace entt + +#endif + + +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 TURN_OFF_DOXYGEN */ 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 Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; + +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; + +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; + +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; + +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::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 = std::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 Type 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 Type 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 = Type; +}; + +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; + +/** + * @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 +constexpr 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 = typename 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 = typename type_list_cat::type; + +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; + +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; +}; + +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A type list. + */ +template +using type_list_unique_t = typename type_list_unique::type; + +/** + * @brief Provides the member constant `value` 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>: std::disjunction...> {}; + +/** + * @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 = typename type_list_diff::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 value. */ + static constexpr auto value = Value; +}; + +/** + * @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 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 +constexpr 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 = typename 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 = typename value_list_cat::type; + +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::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>: std::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>: std::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 std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::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>: std::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` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; + +/*! @copydoc is_complete */ +template +struct is_complete>: std::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` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_iterator_category: std::false_type {}; + +template +struct has_iterator_category::iterator_category>>: std::true_type {}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : 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` 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 + : std::conjunction, std::negation>> {}; + +/** + * @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` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; + +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; + +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_tuple_size_value: std::false_type {}; + +template +struct has_tuple_size_value::value)>>: std::true_type {}; + +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} + +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} + +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; + } +} + +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); + } +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; + +/** + * @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 = std::remove_const_t; +}; + +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; + +/** + * @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 = typename constness_as::type; + +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); + + 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 = std::remove_pointer_t()))>; +}; + +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct compressed_pair_element { + using reference = Type &; + using const_reference = const Type &; + + template>> + compressed_pair_element() + : value{} {} + + template>, compressed_pair_element>>> + compressed_pair_element(Args &&args) + : value{std::forward(args)} {} + + template + compressed_pair_element(std::tuple args, std::index_sequence) + : value{std::forward(std::get(args))...} {} + + [[nodiscard]] reference get() ENTT_NOEXCEPT { + return value; + } + + [[nodiscard]] const_reference get() const ENTT_NOEXCEPT { + return value; + } + +private: + Type value; +}; + +template +struct compressed_pair_element>>: Type { + using reference = Type &; + using const_reference = const Type &; + using base_type = Type; + + template>> + compressed_pair_element() + : base_type{} {} + + template>, compressed_pair_element>>> + compressed_pair_element(Args &&args) + : base_type{std::forward(args)} {} + + template + compressed_pair_element(std::tuple args, std::index_sequence) + : base_type{std::forward(std::get(args))...} {} + + [[nodiscard]] reference get() ENTT_NOEXCEPT { + return *this; + } + + [[nodiscard]] const_reference get() const ENTT_NOEXCEPT { + return *this; + } +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @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. * - * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. + * This constructor is only available when the types that the pair stores + * are both at least default constructible. * - * @tparam Component Type of component to assign or replace. - * @tparam Args Types of arguments to use to construct the component. - * @param entity A valid entity identifier. - * @param args Parameters to use to initialize the component. - * @return A reference to the newly created component. + * @tparam Dummy Dummy template parameter used for internal purposes. + */ + template && std::is_default_constructible_v>> + constexpr compressed_pair() + : 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) = 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) + : first_base{std::forward(arg)}, + second_base{std::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(std::piecewise_construct_t, std::tuple args, std::tuple other) + : first_base{std::move(args), std::index_sequence_for{}}, + second_base{std::move(other), std::index_sequence_for{}} {} + + /** + * @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) = default; + + /** + * @brief Returns the first element that a pair stores. + * @return The first element that a pair stores. + */ + [[nodiscard]] first_type &first() ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /*! @copydoc first */ + [[nodiscard]] const first_type &first() const ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /** + * @brief Returns the second element that a pair stores. + * @return The second element that a pair stores. + */ + [[nodiscard]] second_type &second() ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /*! @copydoc second */ + [[nodiscard]] const second_type &second() const ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /** + * @brief Swaps two compressed pair objects. + * @param other The compressed pair to swap with. + */ + void swap(compressed_pair &other) { + using std::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 + decltype(auto) get() ENTT_NOEXCEPT { + if constexpr(Index == 0u) { + return first(); + } else { + static_assert(Index == 1u, "Index out of bounds"); + return second(); + } + } + + /*! @copydoc get */ + template + decltype(auto) get() const ENTT_NOEXCEPT { + if constexpr(Index == 0u) { + return first(); + } else { + static_assert(Index == 1u, "Index out of bounds"); + 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, std::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 +inline void swap(compressed_pair &lhs, compressed_pair &rhs) { + lhs.swap(rhs); +} + +} // namespace entt + +// disable structured binding support for clang 6, it messes when specializing tuple_size +#if !defined __clang_major__ || __clang_major__ > 6 +namespace std { + +/** + * @brief `std::tuple_size` specialization for `compressed_pair`s. + * @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 +struct tuple_size>: integral_constant {}; + +/** + * @brief `std::tuple_element` specialization for `compressed_pair`s. + * @tparam Index The index of the type to return. + * @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 +struct tuple_element>: conditional { + static_assert(Index < 2u, "Index out of bounds"); +}; + +} // namespace std +#endif + +#endif + +// #include "../core/fwd.hpp" +#ifndef ENTT_CORE_FWD_HPP +#define ENTT_CORE_FWD_HPP + +#include +#include +// #include "../config/config.h" + + +namespace entt { + +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<>; + +} // namespace entt + +#endif + +// #include "../core/iterator.hpp" +#ifndef ENTT_CORE_ITERATOR_HPP +#define ENTT_CORE_ITERATOR_HPP + +#include +#include +#include +// #include "../config/config.h" + + +namespace entt { + +/** + * @brief Helper type to use as pointer with input iterators. + * @tparam Type of wrapped value. + */ +template +struct input_iterator_pointer final { + /*! @brief Pointer type. */ + using pointer = Type *; + + /*! @brief Default copy constructor, deleted on purpose. */ + input_iterator_pointer(const input_iterator_pointer &) = delete; + + /*! @brief Default move constructor. */ + input_iterator_pointer(input_iterator_pointer &&) = default; + + /** + * @brief Constructs a proxy object by move. + * @param val Value to use to initialize the proxy object. + */ + input_iterator_pointer(Type &&val) + : value{std::move(val)} {} + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This proxy object. + */ + input_iterator_pointer &operator=(const input_iterator_pointer &) = delete; + + /** + * @brief Default move assignment operator. + * @return This proxy object. + */ + input_iterator_pointer &operator=(input_iterator_pointer &&) = default; + + /** + * @brief Access operator for accessing wrapped values. + * @return A pointer to the wrapped value. + */ + [[nodiscard]] pointer operator->() ENTT_NOEXCEPT { + return std::addressof(value); + } + +private: + Type value; +}; + +/** + * @brief Utility class to create an iterable object from a pair of iterators. + * @tparam It Type of iterator. + * @tparam Sentinel Type of sentinel. + */ +template +struct iterable_adaptor final { + /*! @brief Value type. */ + using value_type = typename std::iterator_traits::value_type; + /*! @brief Iterator type. */ + using iterator = It; + /*! @brief Sentinel type. */ + using sentinel = Sentinel; + + /*! @brief Default constructor. */ + iterable_adaptor() = default; + + /** + * @brief Creates an iterable object from a pair of iterators. + * @param from Begin iterator. + * @param to End iterator. */ - template - decltype(auto) assign_or_replace(const entity_type entity, Args &&... args) { - ENTT_ASSERT(valid(entity)); - auto *cpool = assure(); - return cpool->has(entity) ? cpool->replace(*this, entity, std::forward(args)...) : cpool->assign(*this, entity, std::forward(args)...); + iterable_adaptor(iterator from, sentinel to) + : first{from}, + last{to} {} + + /** + * @brief Returns an iterator to the beginning. + * @return An iterator to the first element of the range. + */ + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return first; } /** - * @brief Returns a sink object for the given component. - * - * A sink is an opaque object used to connect listeners to components.
- * The sink returned by this function can be used to receive notifications - * whenever a new instance of the given component is created and assigned to - * an entity. - * - * The function type for a listener is equivalent to: - * - * @code{.cpp} - * void(registry &, Entity, Component &); - * @endcode - * - * Listeners are invoked **after** the component has been assigned to the - * entity. The order of invocation of the listeners isn't guaranteed. - * - * @note - * Empty types aren't explicitly instantiated. Therefore, temporary objects - * are returned through signals. They can be caught only by copy or with - * const references. - * - * @sa sink - * - * @tparam Component Type of component of which to get the sink. - * @return A temporary sink object. + * @brief Returns an iterator to the end. + * @return An iterator to the element following the last element of the + * range. */ - template - auto on_construct() ENTT_NOEXCEPT { - return assure()->on_construct.sink(); + [[nodiscard]] sentinel end() const ENTT_NOEXCEPT { + return last; + } + + /*! @copydoc begin */ + [[nodiscard]] iterator cbegin() const ENTT_NOEXCEPT { + return begin(); + } + + /*! @copydoc end */ + [[nodiscard]] sentinel cend() const ENTT_NOEXCEPT { + return end(); } +private: + It first; + Sentinel last; +}; + +} // namespace entt + +#endif + +// #include "../core/utility.hpp" +#ifndef ENTT_CORE_UTILITY_HPP +#define ENTT_CORE_UTILITY_HPP + +#include +// #include "../config/config.h" + + +namespace entt { + +/*! @brief Identity function object (waiting for C++20). */ +struct identity { + /*! @brief Indicates that this is a transparent function object. */ + using is_transparent = void; + /** - * @brief Returns a sink object for the given component. - * - * A sink is an opaque object used to connect listeners to components.
- * The sink returned by this function can be used to receive notifications - * whenever an instance of the given component is explicitly replaced. - * - * The function type for a listener is equivalent to: - * - * @code{.cpp} - * void(registry &, Entity, Component &); - * @endcode - * - * Listeners are invoked **before** the component has been replaced. The - * order of invocation of the listeners isn't guaranteed. - * - * @note - * Empty types aren't explicitly instantiated. Therefore, temporary objects - * are returned through signals. They can be caught only by copy or with - * const references. - * - * @sa sink - * - * @tparam Component Type of component of which to get the sink. - * @return A temporary sink object. + * @brief Returns its argument unchanged. + * @tparam Type Type of the argument. + * @param value The actual argument. + * @return The submitted value as-is. */ - template - auto on_replace() ENTT_NOEXCEPT { - return assure()->on_replace.sink(); + template + [[nodiscard]] constexpr Type &&operator()(Type &&value) const ENTT_NOEXCEPT { + return std::forward(value); } +}; + +/** + * @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) ENTT_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) ENTT_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 Returns a sink object for the given component. - * - * A sink is an opaque object used to connect listeners to components.
- * The sink returned by this function can be used to receive notifications - * whenever an instance of the given component is removed from an entity and - * thus destroyed. - * - * The function type for a listener is equivalent to: - * - * @code{.cpp} - * void(registry &, Entity); - * @endcode - * - * Listeners are invoked **before** the component has been removed from the - * entity. The order of invocation of the listeners isn't guaranteed. - * - * @note - * Empty types aren't explicitly instantiated. Therefore, temporary objects - * are returned through signals. They can be caught only by copy or with - * const references. - * - * @sa sink - * - * @tparam Component Type of component of which to get the sink. - * @return A temporary sink object. + * @brief Constructs a y-combinator from a given function. + * @param recursive A potentially recursive function. */ - template - auto on_destroy() ENTT_NOEXCEPT { - return assure()->on_destroy.sink(); + y_combinator(Func recursive) + : func{std::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 + decltype(auto) operator()(Args &&...args) const { + return func(*this, std::forward(args)...); + } + + /*! @copydoc operator()() */ + template + decltype(auto) operator()(Args &&...args) { + return func(*this, std::forward(args)...); + } + +private: + Func func; +}; + +} // namespace entt + +#endif + +// #include "fwd.hpp" +#ifndef ENTT_RESOURCE_FWD_HPP +#define ENTT_RESOURCE_FWD_HPP + +#include + +namespace entt { + +template +struct resource_loader; + +template, typename = std::allocator> +class resource_cache; + +template +class resource; + +} // namespace entt + +#endif + +// #include "loader.hpp" +#ifndef ENTT_RESOURCE_LOADEr_HPP +#define ENTT_RESOURCE_LOADEr_HPP + +#include +#include +// #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 = std::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 std::make_shared(std::forward(args)...); } +}; + +} // namespace entt + +#endif + +// #include "resource.hpp" +#ifndef ENTT_RESOURCE_RESOURCE_HPP +#define ENTT_RESOURCE_RESOURCE_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #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 { + /*! @brief Resource handles are friends with each other. */ + template + friend class resource; + + template + static constexpr bool is_acceptable_v = !std::is_same_v && std::is_constructible_v; + +public: + /*! @brief Default constructor. */ + resource() ENTT_NOEXCEPT + : value{} {} /** - * @brief Sorts the pool of entities for the given component. - * - * The order of the elements in a pool is highly affected by assignments - * of components to entities and deletions. Components are arranged to - * maximize the performance during iterations and users should not make any - * assumption on the order.
- * This function can be used to impose an order to the elements in the pool - * of the given component. The order is kept valid until a component of the - * given type is assigned or removed from an entity. - * - * 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(const Entity, const Entity); - * bool(const Component &, const Component &); - * @endcode - * - * Moreover, the comparison function object shall induce a - * _strict weak ordering_ on the values. - * - * The sort function oject 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. - * - * The comparison funtion object received by the sort function object hasn't - * necessarily the type of the one passed along with the other parameters to - * this member function. - * - * @warning - * Pools of components that are owned by a group cannot be sorted.
- * An assertion will abort the execution at runtime in debug mode in case - * the pool is owned by a group. - * - * @tparam Component Type of components 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. + * @brief Creates a handle from a weak pointer, namely a resource. + * @param res A weak pointer to a resource. */ - template - void sort(Compare compare, Sort algo = Sort{}, Args &&... args) { - ENTT_ASSERT(!owned()); - assure()->sort(std::move(compare), std::move(algo), std::forward(args)...); + explicit resource(std::shared_ptr res) ENTT_NOEXCEPT + : value{std::move(res)} {} + + /*! @brief Default copy constructor. */ + resource(const resource &) ENTT_NOEXCEPT = default; + + /*! @brief Default move constructor. */ + resource(resource &&) ENTT_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, Type &res) ENTT_NOEXCEPT + : value{other.value, std::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>> + resource(const resource &other) ENTT_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>> + resource(resource &&other) ENTT_NOEXCEPT + : value{std::move(other.value)} {} + + /** + * @brief Default copy assignment operator. + * @return This resource handle. + */ + resource &operator=(const resource &) ENTT_NOEXCEPT = default; + + /** + * @brief Default move assignment operator. + * @return This resource handle. + */ + resource &operator=(resource &&) ENTT_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 + std::enable_if_t, resource &> + operator=(const resource &other) ENTT_NOEXCEPT { + value = other.value; + return *this; } /** - * @brief Sorts two pools of components in the same way. - * - * The order of the elements in a pool is highly affected by assignments - * of components to entities and deletions. Components are arranged to - * maximize the performance during iterations and users should not make any - * assumption on the order. - * - * It happens that different pools of components must be sorted the same way - * because of runtime and/or performance constraints. This function can be - * used to order a pool of components according to the order between the - * entities in another pool of components. - * - * @b How @b it @b works - * - * Being `A` and `B` the two sets where `B` is the master (the one the order - * of which rules) and `A` is the slave (the one to sort), after a call to - * this function an iterator for `A` will return the entities according to - * the following rules: - * - * * All the entities in `A` that are also in `B` are returned first - * according to the order they have in `B`. - * * All the entities in `A` that are not in `B` are returned in no - * particular order after all the other entities. - * - * Any subsequent change to `B` won't affect the order in `A`. - * - * @warning - * Pools of components that are owned by a group cannot be sorted.
- * An assertion will abort the execution at runtime in debug mode in case - * the pool is owned by a group. - * - * @tparam To Type of components to sort. - * @tparam From Type of components to use to sort. + * @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 - void sort() { - ENTT_ASSERT(!owned()); - assure()->respect(*assure()); + template + std::enable_if_t, resource &> + operator=(resource &&other) ENTT_NOEXCEPT { + value = std::move(other.value); + return *this; } /** - * @brief Resets the given component for an entity. - * - * If the entity has an instance of the component, this function removes the - * component from the entity. Otherwise it does nothing. + * @brief Returns a reference to the managed resource. * * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. + * The behavior is undefined if the handle doesn't contain a resource. * - * @tparam Component Type of component to reset. - * @param entity A valid entity identifier. + * @return A reference to the managed resource. */ - template - void reset(const entity_type entity) { - ENTT_ASSERT(valid(entity)); + [[nodiscard]] Type &operator*() const ENTT_NOEXCEPT { + return *value; + } - if(auto *cpool = assure(); cpool->has(entity)) { - cpool->remove(*this, entity); - } + /*! @copydoc operator* */ + [[nodiscard]] operator Type &() const ENTT_NOEXCEPT { + return *value; } /** - * @brief Resets the pool of the given component. - * - * For each entity that has an instance of the given component, the - * component itself is removed and thus destroyed. - * - * @tparam Component Type of component whose pool must be reset. + * @brief Returns a pointer to the managed resource. + * @return A pointer to the managed resource. */ - template - void reset() { - if(auto *cpool = assure(); cpool->on_destroy.empty()) { - // no group set, otherwise the signal wouldn't be empty - cpool->reset(); - } else { - for(const auto entity: static_cast &>(*cpool)) { - cpool->remove(*this, entity); - } - } + [[nodiscard]] Type *operator->() const ENTT_NOEXCEPT { + return value.get(); } /** - * @brief Resets a whole registry. - * - * Destroys all the entities. After a call to `reset`, all the entities - * still in use are recycled with a new version number. In case entity - * identifers are stored around, the `valid` member function can be used - * to know if they are still valid. + * @brief Returns true if a handle contains a resource, false otherwise. + * @return True if the handle contains a resource, false otherwise. */ - void reset() { - each([this](const auto entity) { - // useless this-> used to suppress a warning with clang - this->destroy(entity); - }); + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return static_cast(value); } /** - * @brief Iterates all the entities that are still in use. - * - * The function object is invoked for each entity that is still in use.
- * The signature of the function should be equivalent to the following: - * - * @code{.cpp} - * void(const Entity); - * @endcode - * - * This function is fairly slow and should not be used frequently. However, - * it's useful for iterating all the entities still in use, regardless of - * their components. - * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. + * @brief Returns the number of handles pointing the same resource. + * @return The number of handles pointing the same resource. */ - template - void each(Func func) const { - static_assert(std::is_invocable_v); + [[nodiscard]] long use_count() const ENTT_NOEXCEPT { + return value.use_count(); + } - if(available) { - for(auto pos = entities.size(); pos; --pos) { - const auto curr = entity_type(pos - 1); - const auto entity = entities[curr]; - const auto entt = entity & traits_type::entity_mask; +private: + std::shared_ptr value; +}; - if(curr == entt) { - func(entity); - } - } - } else { - for(auto pos = entities.size(); pos; --pos) { - func(entities[pos-1]); - } - } +/** + * @brief Compares two handles. + * @tparam Res Type of resource managed by the first handle. + * @tparam Other Type of resource managed by the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return True if both handles refer to the same resource, false otherwise. + */ +template +[[nodiscard]] bool operator==(const resource &lhs, const resource &rhs) ENTT_NOEXCEPT { + return (std::addressof(*lhs) == std::addressof(*rhs)); +} + +/** + * @brief Compares two handles. + * @tparam Res Type of resource managed by the first handle. + * @tparam Other Type of resource managed by the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return False if both handles refer to the same registry, true otherwise. + */ +template +[[nodiscard]] bool operator!=(const resource &lhs, const resource &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Compares two handles. + * @tparam Res Type of resource managed by the first handle. + * @tparam Other Type of resource managed by the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return True if the first handle is less than the second, false otherwise. + */ +template +[[nodiscard]] bool operator<(const resource &lhs, const resource &rhs) ENTT_NOEXCEPT { + return (std::addressof(*lhs) < std::addressof(*rhs)); +} + +/** + * @brief Compares two handles. + * @tparam Res Type of resource managed by the first handle. + * @tparam Other Type of resource managed by the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return True if the first handle is greater than the second, false otherwise. + */ +template +[[nodiscard]] bool operator>(const resource &lhs, const resource &rhs) ENTT_NOEXCEPT { + return (std::addressof(*lhs) > std::addressof(*rhs)); +} + +/** + * @brief Compares two handles. + * @tparam Res Type of resource managed by the first handle. + * @tparam Other Type of resource managed by the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return True if the first handle is less than or equal to the second, false + * otherwise. + */ +template +[[nodiscard]] bool operator<=(const resource &lhs, const resource &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +/** + * @brief Compares two handles. + * @tparam Res Type of resource managed by the first handle. + * @tparam Other Type of resource managed by the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return True if the first handle is greater than or equal to the second, + * false otherwise. + */ +template +[[nodiscard]] bool operator>=(const resource &lhs, const resource &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +} // namespace entt + +#endif + + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +class resource_cache_iterator final { + template + friend class resource_cache_iterator; + +public: + using value_type = std::pair>; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + resource_cache_iterator() ENTT_NOEXCEPT = default; + + resource_cache_iterator(const It iter) ENTT_NOEXCEPT + : it{iter} {} + + template && std::is_constructible_v>> + resource_cache_iterator(const resource_cache_iterator, Other> &other) ENTT_NOEXCEPT + : it{other.it} {} + + resource_cache_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; } - /** - * @brief Checks if an entity has components assigned. - * @param entity A valid entity identifier. - * @return True if the entity has no components assigned, false otherwise. - */ - bool orphan(const entity_type entity) const { - ENTT_ASSERT(valid(entity)); - bool orphan = true; + resource_cache_iterator operator++(int) ENTT_NOEXCEPT { + resource_cache_iterator orig = *this; + return ++(*this), orig; + } - for(std::size_t pos{}, last = pools.size(); pos < last && orphan; ++pos) { - const auto &pdata = pools[pos]; - orphan = !(pdata.pool && pdata.pool->has(entity)); - } + resource_cache_iterator &operator--() ENTT_NOEXCEPT { + return --it, *this; + } - return orphan; + resource_cache_iterator operator--(int) ENTT_NOEXCEPT { + resource_cache_iterator orig = *this; + return operator--(), orig; } - /** - * @brief Iterates orphans and applies them the given function object. - * - * The function object is invoked for each entity that is still in use and - * has no components assigned.
- * The signature of the function should be equivalent to the following: - * - * @code{.cpp} - * void(const Entity); - * @endcode - * - * This function can be very slow and should not be used frequently. - * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. - */ - template - void orphans(Func func) const { - static_assert(std::is_invocable_v); + resource_cache_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + it += value; + return *this; + } - each([&func, this](const auto entity) { - if(orphan(entity)) { - func(entity); - } - }); + resource_cache_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + resource_cache_iterator copy = *this; + return (copy += value); } - /** - * @brief Returns a view for the given components. - * - * This kind of objects are created on the fly and share with the registry - * its internal data structures.
- * Feel free to discard a view after the use. Creating and destroying a view - * is an incredibly cheap operation because they do not require any type of - * initialization.
- * As a rule of thumb, storing a view should never be an option. - * - * Views do their best to iterate the smallest set of candidate entities. - * In particular: - * - * * Single component views are incredibly fast and iterate a packed array - * of entities, all of which has the given component. - * * Multi component views look at the number of entities available for each - * component and pick up a reference to the smallest set of candidates to - * test for the given components. - * - * Views in no way affect the functionalities of the registry nor those of - * the underlying pools. - * - * @note - * Multi component views are pretty fast. However their performance tend to - * degenerate when the number of components to iterate grows up and the most - * of the entities have all the given components.
- * To get a performance boost, consider using a group instead. - * - * @tparam Component Type of components used to construct the view. - * @return A newly created view. - */ - template - entt::basic_view view() { - return { assure()... }; + resource_cache_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); } - /*! @copydoc view */ - template - inline entt::basic_view view() const { - static_assert(std::conjunction_v...>); - return const_cast(this)->view(); + resource_cache_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); } - /** - * @brief Checks whether a given component belongs to a group. - * @tparam Component Type of component in which one is interested. - * @return True if the component belongs to a group, false otherwise. - */ - template - bool owned() const ENTT_NOEXCEPT { - const auto *cpool = pool(); - return cpool && cpool->group; + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + return {it[value].first, resource{it[value].second}}; } - /** - * @brief Returns a group for the given components. - * - * This kind of objects are created on the fly and share with the registry - * its internal data structures.
- * Feel free to discard a group after the use. Creating and destroying a - * group is an incredibly cheap operation because they do not require any - * type of initialization, but for the first time they are requested.
- * As a rule of thumb, storing a group should never be an option. - * - * Groups support exclusion lists and can own types of components. The more - * types are owned by a group, the faster it is to iterate entities and - * components.
- * However, groups also affect some features of the registry such as the - * creation and destruction of components, which will consequently be - * slightly slower (nothing that can be noticed in most cases). - * - * @note - * Pools of components that are owned by a group cannot be sorted anymore. - * The group takes the ownership of the pools and arrange components so as - * to iterate them as fast as possible. - * - * @tparam Owned Types of components owned by the group. - * @tparam Get Types of components observed by the group. - * @tparam Exclude Types of components used to filter the group. - * @return A newly created group. - */ - template - inline entt::basic_group, Owned...> group(get_t, exclude_t = {}) { - static_assert(sizeof...(Owned) + sizeof...(Get) > 0); - static_assert(sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude) > 1); + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return (*this)[0]; + } - using handler_type = group_handler, type_list, Owned...>; + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } - const std::size_t extent[] = { sizeof...(Owned), sizeof...(Get), sizeof...(Exclude) }; - const ENTT_ID_TYPE types[] = { type()..., type()..., type()... }; - handler_type *curr = nullptr; + template + friend std::ptrdiff_t operator-(const resource_cache_iterator &, const resource_cache_iterator &) ENTT_NOEXCEPT; - if(auto it = std::find_if(groups.begin(), groups.end(), [&extent, &types](auto &&gdata) { - return std::equal(std::begin(extent), std::end(extent), gdata.extent) && gdata.is_same(types); - }); it != groups.cend()) - { - curr = static_cast(it->group.get()); - } + template + friend bool operator==(const resource_cache_iterator &, const resource_cache_iterator &) ENTT_NOEXCEPT; - if(!curr) { - ENTT_ASSERT(!(owned() || ...)); + template + friend bool operator<(const resource_cache_iterator &, const resource_cache_iterator &) ENTT_NOEXCEPT; - groups.push_back(group_data{ - { sizeof...(Owned), sizeof...(Get), sizeof...(Exclude) }, - decltype(group_data::group){new handler_type{}, +[](void *gptr) { delete static_cast(gptr); }}, - +[](const ENTT_ID_TYPE *other) { - const std::size_t ctypes[] = { type()..., type()..., type()... }; - return std::equal(std::begin(ctypes), std::end(ctypes), other); - } - }); +private: + It it; +}; + +template +[[nodiscard]] std::ptrdiff_t operator-(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it - rhs.it; +} - const auto cpools = std::make_tuple(assure()..., assure()..., assure()...); - curr = static_cast(groups.back().group.get()); +template +[[nodiscard]] bool operator==(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} - ((std::get *>(cpools)->group = curr), ...); - (std::get *>(cpools)->on_construct.sink().template connect<&handler_type::template maybe_valid_if>(curr), ...); - (std::get *>(cpools)->on_destroy.sink().template connect<&handler_type::template discard_if<>>(curr), ...); +template +[[nodiscard]] bool operator!=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} - (std::get *>(cpools)->on_construct.sink().template connect<&handler_type::template maybe_valid_if>(curr), ...); - (std::get *>(cpools)->on_destroy.sink().template connect<&handler_type::template discard_if<>>(curr), ...); +template +[[nodiscard]] bool operator<(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it < rhs.it; +} - (std::get *>(cpools)->on_destroy.sink().template connect<&handler_type::template maybe_valid_if>(curr), ...); - (std::get *>(cpools)->on_construct.sink().template connect<&handler_type::template discard_if>(curr), ...); +template +[[nodiscard]] bool operator>(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} - const auto *cpool = std::min({ - static_cast *>(std::get *>(cpools))..., - static_cast *>(std::get *>(cpools))... - }, [](const auto *lhs, const auto *rhs) { - return lhs->size() < rhs->size(); - }); +template +[[nodiscard]] bool operator<=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} - // we cannot iterate backwards because we want to leave behind valid entities in case of owned types - std::for_each(cpool->data(), cpool->data() + cpool->size(), [curr, &cpools, this](const auto entity) { - if((std::get *>(cpools)->has(entity) && ...) - && (std::get *>(cpools)->has(entity) && ...) - && !(std::get *>(cpools)->has(entity) || ...)) - { - if constexpr(sizeof...(Owned) == 0) { - curr->construct(entity); - // suppress warnings - (void)this; - } else { - const auto pos = curr->owned++; - // useless this-> used to suppress a warning with clang - (this->swap(0, std::get *>(cpools), entity, pos), ...); - } - } - }); - } +template +[[nodiscard]] bool operator>=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} - if constexpr(sizeof...(Owned) == 0) { - return { static_cast *>(curr), pool()... }; - } else { - return { &curr->owned, pool()... , pool()... }; - } - } +} // namespace internal - /*! @copydoc group */ - template - inline entt::basic_group, Owned...> group(get_t, exclude_t = {}) const { - static_assert(std::conjunction_v..., std::is_const...>); - return const_cast(this)->group(entt::get, exclude); - } +/** + * Internal details not to be documented. + * @endcond + */ - /*! @copydoc group */ - template - inline entt::basic_group, Owned...> group(exclude_t = {}) { - return group(entt::get<>, exclude); - } +/** + * @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 = typename std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using container_allocator = typename alloc_traits::template rebind_alloc>; + using container_type = dense_map, container_allocator>; - /*! @copydoc group */ - template - inline entt::basic_group, Owned...> group(exclude_t = {}) const { - static_assert(std::conjunction_v...>); - return const_cast(this)->group(exclude); - } +public: + /*! @brief Resource type. */ + using value_type = Type; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Loader type. */ + using loader_type = Loader; + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @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 Returns a runtime view for the given components. - * - * This kind of objects are created on the fly and share with the registry - * its internal data structures.
- * Users should throw away the view after use. Fortunately, creating and - * destroying a runtime view is an incredibly cheap operation because they - * do not require any type of initialization.
- * As a rule of thumb, storing a view should never be an option. - * - * Runtime views are to be used when users want to construct a view from - * some external inputs and don't know at compile-time what are the required - * components.
- * This is particularly well suited to plugin systems and mods in general. - * - * @tparam It Type of forward iterator. - * @param first An iterator to the first element of the range of components. - * @param last An iterator past the last element of the range of components. - * @return A newly created runtime view. + * @brief Constructs an empty cache with a given allocator. + * @param allocator The allocator to use. */ - template - entt::basic_runtime_view runtime_view(It first, It last) const { - static_assert(std::is_convertible_v::value_type, component_type>); - std::vector *> set(std::distance(first, last)); + explicit resource_cache(const allocator_type &allocator) + : resource_cache{loader_type{}, allocator} {} - std::transform(first, last, set.begin(), [this](const component_type ctype) { - auto it = std::find_if(pools.begin(), pools.end(), [ctype](const auto &pdata) { - return pdata.pool && pdata.runtime_type == ctype; - }); + /** + * @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} {} - return it != pools.cend() && it->pool ? it->pool.get() : nullptr; - }); + /*! @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{std::piecewise_construct, std::forward_as_tuple(other.pool.first(), allocator), std::forward_as_tuple(other.pool.second())} {} - return { std::move(set) }; + /*! @brief Default move constructor. */ + resource_cache(resource_cache &&) = 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{std::piecewise_construct, std::forward_as_tuple(std::move(other.pool.first()), allocator), std::forward_as_tuple(std::move(other.pool.second()))} {} + + /** + * @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 &&) = default; + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return pool.first().get_allocator(); } /** - * @brief Clones the given components and all the entity identifiers. - * - * The components must be copiable for obvious reasons. The entities - * maintain their versions once copied.
- * If no components are provided, the registry will try to clone all the - * existing pools. - * - * @note - * There isn't an efficient way to know if all the entities are assigned at - * least one component once copied. Therefore, there may be orphans. It is - * up to the caller to clean up the registry if necessary. - * - * @note - * Listeners and groups aren't copied. It is up to the caller to connect the - * listeners of interest to the new registry and to set up groups. + * @brief Returns an iterator to the beginning. * - * @warning - * Attempting to clone components that aren't copyable results in unexpected - * behaviors.
- * A static assertion will abort the compilation when the components - * provided aren't copy constructible. Otherwise, an assertion will abort - * the execution at runtime in debug mode in case one or more pools cannot - * be cloned. + * The returned iterator points to the first instance of the cache. If the + * cache is empty, the returned iterator will be equal to `end()`. * - * @tparam Component Types of components to clone. - * @return A fresh copy of the registry. + * @return An iterator to the first instance of the internal cache. */ - template - basic_registry clone() const { - static_assert(std::conjunction_v...>); - basic_registry other; + [[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT { + return pool.first().begin(); + } - other.pools.resize(pools.size()); + /*! @copydoc cbegin */ + [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT { + return cbegin(); + } - for(auto pos = pools.size(); pos; --pos) { - if(auto &pdata = pools[pos-1]; pdata.pool && (!sizeof...(Component) || ... || (pdata.runtime_type == type()))) { - auto &curr = other.pools[pos-1]; - curr.clone = pdata.clone; - curr.pool = curr.clone(*pdata.pool); - curr.runtime_type = pdata.runtime_type; - ENTT_ASSERT(sizeof...(Component) == 0 || curr.pool); - } - } + /*! @copydoc begin */ + [[nodiscard]] iterator begin() ENTT_NOEXCEPT { + return pool.first().begin(); + } + + /** + * @brief Returns an iterator to the end. + * + * The returned iterator points to the element following the last instance + * of the cache. Attempting to dereference the returned iterator results in + * undefined behavior. + * + * @return An iterator to the element following the last instance of the + * internal cache. + */ + [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { + return pool.first().end(); + } - other.skip_family_pools = skip_family_pools; - other.entities = entities; - other.available = available; - other.next = next; + /*! @copydoc cend */ + [[nodiscard]] const_iterator end() const ENTT_NOEXCEPT { + return cend(); + } - other.pools.erase(std::remove_if(other.pools.begin()+skip_family_pools, other.pools.end(), [](const auto &pdata) { - return !pdata.pool; - }), other.pools.end()); + /*! @copydoc end */ + [[nodiscard]] iterator end() ENTT_NOEXCEPT { + return pool.first().end(); + } - return other; + /** + * @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 ENTT_NOEXCEPT { + return pool.first().empty(); } /** - * @brief Returns a temporary object to use to create snapshots. - * - * A snapshot is either a full or a partial dump of a registry.
- * It can be used to save and restore its internal state or to keep two or - * more instances of this class in sync, as an example in a client-server - * architecture. - * - * @return A temporary object to use to take snasphosts. + * @brief Number of resources managed by a cache. + * @return Number of resources currently stored. */ - entt::basic_snapshot snapshot() const ENTT_NOEXCEPT { - using follow_fn_type = entity_type(const basic_registry &, const entity_type); - const entity_type seed = available ? (next | (entities[next] & (traits_type::version_mask << traits_type::entity_shift))) : next; - - follow_fn_type *follow = [](const basic_registry ®, const entity_type entity) -> entity_type { - const auto &others = reg.entities; - const auto entt = entity & traits_type::entity_mask; - const auto curr = others[entt] & traits_type::entity_mask; - return (curr | (others[curr] & (traits_type::version_mask << traits_type::entity_shift))); - }; + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return pool.first().size(); + } - return { this, seed, follow }; + /*! @brief Clears a cache. */ + void clear() ENTT_NOEXCEPT { + pool.first().clear(); } /** - * @brief Returns a temporary object to use to load snapshots. + * @brief Loads a resource, if its identifier does not exist. * - * A snapshot is either a full or a partial dump of a registry.
- * It can be used to save and restore its internal state or to keep two or - * more instances of this class in sync, as an example in a client-server - * architecture. + * Arguments are forwarded directly to the loader and _consumed_ only if the + * resource doesn't already exist. * - * @note - * The loader returned by this function requires that the registry be empty. - * In case it isn't, all the data will be automatically deleted before to - * return. + * @warning + * If the resource isn't loaded correctly, the returned handle could be + * invalid and any use of it will result in undefined behavior. * - * @return A temporary object to use to load snasphosts. + * @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. */ - basic_snapshot_loader loader() ENTT_NOEXCEPT { - using force_fn_type = void(basic_registry &, const entity_type, const bool); - - force_fn_type *force = [](basic_registry ®, const entity_type entity, const bool destroyed) { - using promotion_type = std::conditional_t= sizeof(entity_type), size_type, entity_type>; - // explicit promotion to avoid warnings with std::uint16_t - const auto entt = promotion_type{entity} & traits_type::entity_mask; - auto &others = reg.entities; - - if(!(entt < others.size())) { - auto curr = others.size(); - others.resize(entt + 1); - std::iota(others.data() + curr, others.data() + entt, entity_type(curr)); - } - - others[entt] = entity; - - if(destroyed) { - reg.destroy(entity); - const auto version = entity & (traits_type::version_mask << traits_type::entity_shift); - others[entt] = ((others[entt] & traits_type::entity_mask) | version); - } - }; + template + std::pair load(const id_type id, Args &&...args) { + if(auto it = pool.first().find(id); it != pool.first().end()) { + return {it, false}; + } - reset(); - entities.clear(); - available = {}; + return pool.first().emplace(id, pool.second()(std::forward(args)...)); + } - return { this, force }; + /** + * @brief Force loads a resource, if its identifier does not exist. + * @copydetails load + */ + template + std::pair force_load(const id_type id, Args &&...args) { + return {pool.first().insert_or_assign(id, pool.second()(std::forward(args)...)).first, true}; } /** - * @brief Binds an object to the context of the registry. + * @brief Returns a handle for a given resource identifier. * - * If the value already exists it is overwritten, otherwise a new instance - * of the given type is created and initialized with the arguments provided. + * @warning + * There is no guarantee that the returned handle is valid.
+ * If it is not, any use will result in indefinite behavior. * - * @tparam Type Type of object to set. - * @tparam Args Types of arguments to use to construct the object. - * @param args Parameters to use to initialize the value. - * @return A reference to the newly created object. + * @param id Unique resource identifier. + * @return A handle for the given resource. */ - template - Type & set(Args &&... args) { - const auto ctype = runtime_type(); - auto it = std::find_if(vars.begin(), vars.end(), [ctype](const auto &candidate) { - return candidate.runtime_type == ctype; - }); + [[nodiscard]] resource operator[](const id_type id) const { + if(auto it = pool.first().find(id); it != pool.first().cend()) { + return resource{it->second}; + } - if(it == vars.cend()) { - vars.push_back({ - decltype(ctx_variable::value){new Type{std::forward(args)...}, +[](void *ptr) { delete static_cast(ptr); }}, - ctype - }); + return {}; + } - it = std::prev(vars.end()); - } else { - it->value.reset(new Type{std::forward(args)...}); + /*! @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 *static_cast(it->value.get()); + return {}; } /** - * @brief Unsets a context variable if it exists. - * @tparam Type Type of object to set. + * @brief Checks if a cache contains a given identifier. + * @param id Unique resource identifier. + * @return True if the cache contains the resource, false otherwise. */ - template - void unset() { - vars.erase(std::remove_if(vars.begin(), vars.end(), [](auto &var) { - return var.runtime_type == runtime_type(); - }), vars.end()); + [[nodiscard]] bool contains(const id_type id) const { + return pool.first().contains(id); } /** - * @brief Binds an object to the context of the registry. - * - * In case the context doesn't contain the given object, the parameters - * provided are used to construct it. - * - * @tparam Type Type of object to set. - * @tparam Args Types of arguments to use to construct the object. - * @param args Parameters to use to initialize the object. - * @return Reference to the object. + * @brief Removes an element from a given position. + * @param pos An iterator to the element to remove. + * @return An iterator following the removed element. */ - template - Type & ctx_or_set(Args &&... args) { - auto *type = try_ctx(); - return type ? *type : set(std::forward(args)...); + iterator erase(const_iterator pos) { + const auto it = pool.first().begin(); + return pool.first().erase(it + (pos - const_iterator{it})); } /** - * @brief Returns a pointer to an object in the context of the registry. - * @tparam Type Type of object to get. - * @return A pointer to the object if it exists in the context of the - * registry, a null pointer otherwise. + * @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. */ - template - const Type * try_ctx() const ENTT_NOEXCEPT { - const auto it = std::find_if(vars.begin(), vars.end(), [](const auto &var) { - return var.runtime_type == runtime_type(); - }); - - return (it == vars.cend()) ? nullptr : static_cast(it->value.get()); - } - - /*! @copydoc try_ctx */ - template - inline Type * try_ctx() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).template try_ctx()); + 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 Returns a reference to an object in the context of the registry. - * - * @warning - * Attempting to get a context variable that doesn't exist results in - * undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid requests. - * - * @tparam Type Type of object to get. - * @return A valid reference to the object in the context of the registry. + * @brief Removes the given elements from a cache. + * @param id Unique resource identifier. + * @return Number of resources erased (either 0 or 1). */ - template - const Type & ctx() const ENTT_NOEXCEPT { - const auto *instance = try_ctx(); - ENTT_ASSERT(instance); - return *instance; + size_type erase(const id_type id) { + return pool.first().erase(id); } - /*! @copydoc ctx */ - template - inline Type & ctx() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).template ctx()); + /** + * @brief Returns the loader used to create resources. + * @return The loader used to create resources. + */ + [[nodiscard]] loader_type loader() const { + return pool.second(); } private: - std::size_t skip_family_pools{}; - std::vector pools; - std::vector groups; - std::vector vars; - std::vector entities; - size_type available{}; - entity_type next{}; + compressed_pair pool; }; +} // namespace entt -} +#endif + +// #include "resource/loader.hpp" +#ifndef ENTT_RESOURCE_LOADEr_HPP +#define ENTT_RESOURCE_LOADEr_HPP +#include +#include +// #include "fwd.hpp" -#endif // ENTT_ENTITY_REGISTRY_HPP -// #include "entity.hpp" +namespace entt { -// #include "fwd.hpp" +/** + * @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 = std::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 std::make_shared(std::forward(args)...); + } +}; +} // namespace entt -namespace entt { +#endif + +// #include "resource/resource.hpp" +#ifndef ENTT_RESOURCE_RESOURCE_HPP +#define ENTT_RESOURCE_RESOURCE_HPP + +#include +#include +#include +// #include "../config/config.h" + +// #include "fwd.hpp" +namespace entt { + /** - * @brief Dedicated to those who aren't confident with entity-component systems. + * @brief Basic resource handle. * - * Tiny wrapper around a registry, for all those users that aren't confident - * with entity-component systems and prefer to iterate objects directly. + * 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 Entity A valid entity type (see entt_traits for more details). + * @tparam Type Type of resource managed by a handle. */ -template -struct basic_actor { - /*! @brief Type of registry used internally. */ - using registry_type = basic_registry; - /*! @brief Underlying entity identifier. */ - using entity_type = Entity; +template +class resource { + /*! @brief Resource handles are friends with each other. */ + template + friend class resource; + + template + static constexpr bool is_acceptable_v = !std::is_same_v && std::is_constructible_v; + +public: + /*! @brief Default constructor. */ + resource() ENTT_NOEXCEPT + : value{} {} /** - * @brief Constructs an actor by using the given registry. - * @param ref An entity-component system properly initialized. + * @brief Creates a handle from a weak pointer, namely a resource. + * @param res A weak pointer to a resource. */ - basic_actor(registry_type &ref) - : reg{&ref}, entt{ref.create()} - {} + explicit resource(std::shared_ptr res) ENTT_NOEXCEPT + : value{std::move(res)} {} - /*! @brief Default destructor. */ - virtual ~basic_actor() { - reg->destroy(entt); - } + /*! @brief Default copy constructor. */ + resource(const resource &) ENTT_NOEXCEPT = default; + + /*! @brief Default move constructor. */ + resource(resource &&) ENTT_NOEXCEPT = default; /** - * @brief Move constructor. - * - * After actor move construction, instances that have been moved from are - * placed in a valid but unspecified state. It's highly discouraged to - * continue using them. - * - * @param other The instance to move from. + * @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. */ - basic_actor(basic_actor &&other) - : reg{other.reg}, entt{other.entt} - { - other.entt = null; - } + template + resource(const resource &other, Type &res) ENTT_NOEXCEPT + : value{other.value, std::addressof(res)} {} /** - * @brief Move assignment operator. - * - * After actor move assignment, instances that have been moved from are - * placed in a valid but unspecified state. It's highly discouraged to - * continue using them. - * - * @param other The instance to move from. - * @return This actor. + * @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. */ - basic_actor & operator=(basic_actor &&other) { - if(this != &other) { - auto tmp{std::move(other)}; - std::swap(reg, tmp.reg); - std::swap(entt, tmp.entt); - } + template>> + resource(const resource &other) ENTT_NOEXCEPT + : value{other.value} {} - return *this; - } + /** + * @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>> + resource(resource &&other) ENTT_NOEXCEPT + : value{std::move(other.value)} {} /** - * @brief Assigns the given component to an actor. - * - * A new instance of the given component is created and initialized with the - * arguments provided (the component must have a proper constructor or be of - * aggregate type). Then the component is assigned to the actor.
- * In case the actor already has a component of the given type, it's - * replaced with the new one. - * - * @tparam Component Type of the component to create. - * @tparam Args Types of arguments to use to construct the component. - * @param args Parameters to use to initialize the component. - * @return A reference to the newly created component. + * @brief Default copy assignment operator. + * @return This resource handle. */ - template - decltype(auto) assign(Args &&... args) { - return reg->template assign_or_replace(entt, std::forward(args)...); - } + resource &operator=(const resource &) ENTT_NOEXCEPT = default; /** - * @brief Removes the given component from an actor. - * @tparam Component Type of the component to remove. + * @brief Default move assignment operator. + * @return This resource handle. */ - template - void remove() { - reg->template remove(entt); + resource &operator=(resource &&) ENTT_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 + std::enable_if_t, resource &> + operator=(const resource &other) ENTT_NOEXCEPT { + value = other.value; + return *this; } /** - * @brief Checks if an actor has the given component. - * @tparam Component Type of the component for which to perform the check. - * @return True if the actor has the component, false otherwise. + * @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 - bool has() const ENTT_NOEXCEPT { - return reg->template has(entt); + template + std::enable_if_t, resource &> + operator=(resource &&other) ENTT_NOEXCEPT { + value = std::move(other.value); + return *this; } /** - * @brief Returns references to the given components for an actor. - * @tparam Component Types of components to get. - * @return References to the components owned by the actor. + * @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. */ - template - decltype(auto) get() const ENTT_NOEXCEPT { - return std::as_const(*reg).template get(entt); + [[nodiscard]] Type &operator*() const ENTT_NOEXCEPT { + return *value; } - /*! @copydoc get */ - template - decltype(auto) get() ENTT_NOEXCEPT { - return reg->template get(entt); + /*! @copydoc operator* */ + [[nodiscard]] operator Type &() const ENTT_NOEXCEPT { + return *value; } /** - * @brief Returns pointers to the given components for an actor. - * @tparam Component Types of components to get. - * @return Pointers to the components owned by the actor. + * @brief Returns a pointer to the managed resource. + * @return A pointer to the managed resource. */ - template - auto try_get() const ENTT_NOEXCEPT { - return std::as_const(*reg).template try_get(entt); + [[nodiscard]] Type *operator->() const ENTT_NOEXCEPT { + return value.get(); } - /*! @copydoc try_get */ - template - auto try_get() ENTT_NOEXCEPT { - return reg->template try_get(entt); + /** + * @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 ENTT_NOEXCEPT { + return static_cast(value); } /** - * @brief Returns a reference to the underlying registry. - * @return A reference to the underlying registry. + * @brief Returns the number of handles pointing the same resource. + * @return The number of handles pointing the same resource. */ - inline const registry_type & backend() const ENTT_NOEXCEPT { - return *reg; + [[nodiscard]] long use_count() const ENTT_NOEXCEPT { + return value.use_count(); } - /*! @copydoc backend */ - inline registry_type & backend() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).backend()); - } +private: + std::shared_ptr value; +}; + +/** + * @brief Compares two handles. + * @tparam Res Type of resource managed by the first handle. + * @tparam Other Type of resource managed by the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return True if both handles refer to the same resource, false otherwise. + */ +template +[[nodiscard]] bool operator==(const resource &lhs, const resource &rhs) ENTT_NOEXCEPT { + return (std::addressof(*lhs) == std::addressof(*rhs)); +} + +/** + * @brief Compares two handles. + * @tparam Res Type of resource managed by the first handle. + * @tparam Other Type of resource managed by the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return False if both handles refer to the same registry, true otherwise. + */ +template +[[nodiscard]] bool operator!=(const resource &lhs, const resource &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Compares two handles. + * @tparam Res Type of resource managed by the first handle. + * @tparam Other Type of resource managed by the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return True if the first handle is less than the second, false otherwise. + */ +template +[[nodiscard]] bool operator<(const resource &lhs, const resource &rhs) ENTT_NOEXCEPT { + return (std::addressof(*lhs) < std::addressof(*rhs)); +} + +/** + * @brief Compares two handles. + * @tparam Res Type of resource managed by the first handle. + * @tparam Other Type of resource managed by the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return True if the first handle is greater than the second, false otherwise. + */ +template +[[nodiscard]] bool operator>(const resource &lhs, const resource &rhs) ENTT_NOEXCEPT { + return (std::addressof(*lhs) > std::addressof(*rhs)); +} + +/** + * @brief Compares two handles. + * @tparam Res Type of resource managed by the first handle. + * @tparam Other Type of resource managed by the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return True if the first handle is less than or equal to the second, false + * otherwise. + */ +template +[[nodiscard]] bool operator<=(const resource &lhs, const resource &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +/** + * @brief Compares two handles. + * @tparam Res Type of resource managed by the first handle. + * @tparam Other Type of resource managed by the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return True if the first handle is greater than or equal to the second, + * false otherwise. + */ +template +[[nodiscard]] bool operator>=(const resource &lhs, const resource &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +} // namespace entt + +#endif + +// #include "signal/delegate.hpp" +#ifndef ENTT_SIGNAL_DELEGATE_HPP +#define ENTT_SIGNAL_DELEGATE_HPP + +#include +#include +#include +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "../core/type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP + +#include +#include +#include +#include +// #include "../config/config.h" +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H + +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif + + +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 + +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) + +#endif + + +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 + +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif + +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "fwd.hpp" +#ifndef ENTT_CORE_FWD_HPP +#define ENTT_CORE_FWD_HPP + +#include +#include +// #include "../config/config.h" + - /** - * @brief Returns the entity associated with an actor. - * @return The entity associated with the actor. - */ - inline entity_type entity() const ENTT_NOEXCEPT { - return entt; - } +namespace entt { -private: - registry_type *reg; - Entity entt; -}; +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<>; +} // namespace entt -#endif // ENTT_ENTITY_ACTOR_HPP +#endif -// #include "entity/entity.hpp" -// #include "entity/group.hpp" +namespace entt { -// #include "entity/helper.hpp" -#ifndef ENTT_ENTITY_HELPER_HPP -#define ENTT_ENTITY_HELPER_HPP +/** + * @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 TURN_OFF_DOXYGEN */ choice_t /*! @endcond */ +{}; +/*! @copybrief choice_t */ +template<> +struct choice_t<0> {}; -#include -// #include "../config/config.h" +/** + * @brief Variable template for the choice trick. + * @tparam N Number of choices available. + */ +template +inline constexpr choice_t choice{}; -// #include "../core/hashed_string.hpp" +/** + * @brief Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; -// #include "../signal/sigh.hpp" +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; -// #include "registry.hpp" +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::size_t size_of_v = size_of::value; -namespace entt { +/** + * @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 Converts a registry to a view. - * @tparam Const Constness of the accepted registry. - * @tparam Entity A valid entity type (see entt_traits for more details). + * @brief Wraps a static constant. + * @tparam Value A static constant. */ -template -struct as_view { - /*! @brief Type of registry to convert. */ - using registry_type = std::conditional_t, entt::basic_registry>; +template +using integral_constant = std::integral_constant; - /** - * @brief Constructs a converter for a given registry. - * @param source A valid reference to a registry. - */ - as_view(registry_type &source) ENTT_NOEXCEPT: reg{source} {} +/** + * @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 Conversion function from a registry to a view. - * @tparam Component Type of components used to construct the view. - * @return A newly created view. - */ - template - inline operator entt::basic_view() const { - return reg.template view(); - } +/** + * @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); +}; -private: - registry_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 Type 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 Type 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 = Type; }; +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; /** - * @brief Deduction guide. - * - * It allows to deduce the constness of a registry directly from the instance - * provided to the constructor. - * - * @tparam Entity A valid entity type (see entt_traits for more details). + * @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 -as_view(basic_registry &) ENTT_NOEXCEPT -> as_view; +template +constexpr type_list operator+(type_list, type_list) { + return {}; +} +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_cat; -/*! @copydoc as_view */ -template -as_view(const basic_registry &) ENTT_NOEXCEPT -> as_view; +/*! @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 = typename type_list_cat, List...>::type; +}; /** - * @brief Converts a registry to a group. - * @tparam Const Constness of the accepted registry. - * @tparam Entity A valid entity type (see entt_traits for more details). + * @brief Concatenates multiple type lists. + * @tparam Type Types provided by the type list. */ -template -struct as_group { - /*! @brief Type of registry to convert. */ - using registry_type = std::conditional_t, entt::basic_registry>; +template +struct type_list_cat> { + /*! @brief A type list composed by the types of all the type lists. */ + using type = type_list; +}; - /** - * @brief Constructs a converter for a given registry. - * @param source A valid reference to a registry. - */ - as_group(registry_type &source) ENTT_NOEXCEPT: reg{source} {} +/** + * @brief Helper type. + * @tparam List Type lists to concatenate. + */ +template +using type_list_cat_t = typename type_list_cat::type; - /** - * @brief Conversion function from a registry to a group. - * - * @note - * Unfortunately, only full owning groups are supported because of an issue - * with msvc that doesn't manage to correctly deduce types. - * - * @tparam Owned Types of components owned by the group. - * @return A newly created group. - */ - template - inline operator entt::basic_group, Owned...>() const { - return reg.template group(); - } +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; -private: - registry_type ® +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; }; +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; /** - * @brief Deduction guide. - * - * It allows to deduce the constness of a registry directly from the instance - * provided to the constructor. - * - * @tparam Entity A valid entity type (see entt_traits for more details). + * @brief Helper type. + * @tparam Type A type list. */ -template -as_group(basic_registry &) ENTT_NOEXCEPT -> as_group; - +template +using type_list_unique_t = typename type_list_unique::type; -/*! @copydoc as_group */ -template -as_group(const basic_registry &) ENTT_NOEXCEPT -> as_group; +/** + * @brief Provides the member constant `value` 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>: std::disjunction...> {}; /** - * @brief Dependency function prototype. - * - * A _dependency function_ is a built-in listener to use to automatically assign - * components to an entity when a type has a dependency on some other types. - * - * This is a prototype function to use to create dependencies.
- * It isn't intended for direct use, although nothing forbids using it freely. - * - * @tparam Entity A valid entity type (see entt_traits for more details). - * @tparam Component Type of component that triggers the dependency handler. - * @tparam Dependency Types of components to assign to an entity if triggered. - * @param reg A valid reference to a registry. - * @param entt A valid entity identifier. + * @brief Helper variable template. + * @tparam List Type list. + * @tparam Type Type to look for. */ -template -void dependency(basic_registry ®, const Entity entt, const Component &) { - ((reg.template has(entt) ? void() : (reg.template assign(entt), void())), ...); -} +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 Connects a dependency function to the given sink. - * - * A _dependency function_ is a built-in listener to use to automatically assign - * components to an entity when a type has a dependency on some other types. - * - * The following adds components `a_type` and `another_type` whenever `my_type` - * is assigned to an entity: - * @code{.cpp} - * entt::registry registry; - * entt::connect(registry.construction()); - * @endcode - * - * @tparam Dependency Types of components to assign to an entity if triggered. - * @tparam Component Type of component that triggers the dependency handler. - * @tparam Entity A valid entity type (see entt_traits for more details). - * @param sink A sink object properly initialized. + * @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 -inline void connect(sink &, const Entity, Component &)> sink) { - sink.template connect>(); -} +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 = typename type_list_diff::type; /** - * @brief Disconnects a dependency function from the given sink. - * - * A _dependency function_ is a built-in listener to use to automatically assign - * components to an entity when a type has a dependency on some other types. - * - * The following breaks the dependency between the component `my_type` and the - * components `a_type` and `another_type`: - * @code{.cpp} - * entt::registry registry; - * entt::disconnect(registry.construction()); - * @endcode - * - * @tparam Dependency Types of components used to create the dependency. - * @tparam Component Type of component that triggers the dependency handler. - * @tparam Entity A valid entity type (see entt_traits for more details). - * @param sink A sink object properly initialized. + * @brief A class to use to push around lists of constant values, nothing more. + * @tparam Value Values provided by the value list. */ -template -inline void disconnect(sink &, const Entity, Component &)> sink) { - sink.template disconnect>(); -} +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 Alias template to ease the assignment of tags to entities. - * - * If used in combination with hashed strings, it simplifies the assignment of - * tags to entities and the use of tags in general where a type would be - * required otherwise.
- * As an example and where the user defined literal for hashed strings hasn't - * been changed: - * @code{.cpp} - * entt::registry registry; - * registry.assign>(entity); - * @endcode - * - * @note - * Tags are empty components and therefore candidates for the empty component - * optimization. - * - * @tparam Value The numeric representation of an instance of hashed string. + * @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 -using tag = std::integral_constant; +template +struct value_list_element<0u, value_list> { + /*! @brief Searched value. */ + static constexpr auto value = Value; +}; +/** + * @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 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 +constexpr 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 = typename value_list_cat, List...>::type; +}; -#endif // ENTT_ENTITY_HELPER_HPP +/** + * @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; +}; -// #include "entity/prototype.hpp" -#ifndef ENTT_ENTITY_PROTOTYPE_HPP -#define ENTT_ENTITY_PROTOTYPE_HPP +/** + * @brief Helper type. + * @tparam List Value lists to concatenate. + */ +template +using value_list_cat_t = typename value_list_cat::type; +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::false_type {}; -#include -#include -#include -#include -#include -// #include "../config/config.h" +/** + * @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>: std::is_invocable {}; -// #include "registry.hpp" +/** + * @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>: std::is_invocable {}; -// #include "entity.hpp" +/** + * @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; -// #include "fwd.hpp" +/*! @brief Same as std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::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>: std::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; -namespace entt { +/** + * @brief Provides the member constant `value` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; +/*! @copydoc is_complete */ +template +struct is_complete>: std::true_type {}; /** - * @brief Prototype container for _concepts_. - * - * A prototype is used to define a _concept_ in terms of components.
- * Prototypes act as templates for those specific types of an application which - * users would otherwise define through a series of component assignments to - * entities. In other words, prototypes can be used to assign components to - * entities of a registry at once. - * - * @note - * Components used along with prototypes must be copy constructible. Prototypes - * wrap component types with custom types, so they do not interfere with other - * users of the registry they were built with. - * - * @warning - * Prototypes directly use their underlying registries to store entities and - * components for their purposes. Users must ensure that the lifetime of a - * registry and its contents exceed that of the prototypes that use it. - * - * @tparam Entity A valid entity type (see entt_traits for more details). + * @brief Helper variable template. + * @tparam Type The type to test. */ -template -class basic_prototype { - using basic_fn_type = void(const basic_prototype &, basic_registry &, const Entity); - using component_type = typename basic_registry::component_type; +template +inline constexpr bool is_complete_v = is_complete::value; - template - struct component_wrapper { Component component; }; +/** + * @brief Provides the member constant `value` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; - struct component_handler { - basic_fn_type *assign_or_replace; - basic_fn_type *assign; - }; +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ - void release() { - if(reg->valid(entity)) { - reg->destroy(entity); - } - } +namespace internal { -public: - /*! @brief Registry type. */ - using registry_type = basic_registry; - /*! @brief Underlying entity identifier. */ - using entity_type = Entity; - /*! @brief Unsigned integer type. */ - using size_type = std::size_t; +template +struct has_iterator_category: std::false_type {}; - /** - * @brief Constructs a prototype that is bound to a given registry. - * @param ref A valid reference to a registry. - */ - basic_prototype(registry_type &ref) - : reg{&ref}, - entity{ref.create()} - {} +template +struct has_iterator_category::iterator_category>>: std::true_type {}; - /** - * @brief Releases all its resources. - */ - ~basic_prototype() { - release(); - } +} // namespace internal - /** - * @brief Move constructor. - * - * After prototype move construction, instances that have been moved from - * are placed in a valid but unspecified state. It's highly discouraged to - * continue using them. - * - * @param other The instance to move from. - */ - basic_prototype(basic_prototype &&other) - : handlers{std::move(other.handlers)}, - reg{other.reg}, - entity{other.entity} - { - other.entity = null; - } +/** + * Internal details not to be documented. + * @endcond + */ - /** - * @brief Move assignment operator. - * - * After prototype move assignment, instances that have been moved from are - * placed in a valid but unspecified state. It's highly discouraged to - * continue using them. - * - * @param other The instance to move from. - * @return This prototype. - */ - basic_prototype & operator=(basic_prototype &&other) { - if(this != &other) { - auto tmp{std::move(other)}; - handlers.swap(tmp.handlers); - std::swap(reg, tmp.reg); - std::swap(entity, tmp.entity); - } +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : internal::has_iterator_category {}; - return *this; - } +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_iterator_v = is_iterator::value; - /** - * @brief Assigns to or replaces the given component of a prototype. - * @tparam Component Type of component to assign or replace. - * @tparam Args Types of arguments to use to construct the component. - * @param args Parameters to use to initialize the component. - * @return A reference to the newly created component. - */ - template - Component & set(Args &&... args) { - component_handler handler; +/** + * @brief Provides the member constant `value` 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 + : std::conjunction, std::negation>> {}; - handler.assign_or_replace = [](const basic_prototype &proto, registry_type &other, const Entity dst) { - const auto &wrapper = proto.reg->template get>(proto.entity); - other.template assign_or_replace(dst, wrapper.component); - }; +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_ebco_eligible_v = is_ebco_eligible::value; - handler.assign = [](const basic_prototype &proto, registry_type &other, const Entity dst) { - if(!other.template has(dst)) { - const auto &wrapper = proto.reg->template get>(proto.entity); - other.template assign(dst, wrapper.component); - } - }; +/** + * @brief Provides the member constant `value` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; - handlers[reg->template type()] = handler; - auto &wrapper = reg->template assign_or_replace>(entity, Component{std::forward(args)...}); - return wrapper.component; - } +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; - /** - * @brief Removes the given component from a prototype. - * @tparam Component Type of component to remove. - */ - template - void unset() ENTT_NOEXCEPT { - reg->template reset>(entity); - handlers.erase(reg->template type()); - } +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; - /** - * @brief Checks if a prototype owns all the given components. - * @tparam Component Components for which to perform the check. - * @return True if the prototype owns all the components, false otherwise. - */ - template - bool has() const ENTT_NOEXCEPT { - return reg->template has...>(entity); - } +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; - /** - * @brief Returns references to the given components. - * - * @warning - * Attempting to get a component from a prototype that doesn't own it - * results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * prototype doesn't own an instance of the given component. - * - * @tparam Component Types of components to get. - * @return References to the components owned by the prototype. - */ - template - decltype(auto) get() const ENTT_NOEXCEPT { - if constexpr(sizeof...(Component) == 1) { - return (std::as_const(*reg).template get>(entity).component); - } else { - return std::tuple &...>{get()...}; - } - } +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ - /*! @copydoc get */ - template - inline decltype(auto) get() ENTT_NOEXCEPT { - if constexpr(sizeof...(Component) == 1) { - return (const_cast(std::as_const(*this).template get()), ...); - } else { - return std::tuple{get()...}; - } - } +namespace internal { - /** - * @brief Returns pointers to the given components. - * @tparam Component Types of components to get. - * @return Pointers to the components owned by the prototype. - */ - template - auto try_get() const ENTT_NOEXCEPT { - if constexpr(sizeof...(Component) == 1) { - const auto *wrapper = reg->template try_get>(entity); - return wrapper ? &wrapper->component : nullptr; - } else { - return std::tuple *...>{try_get()...}; - } - } +template +struct has_tuple_size_value: std::false_type {}; - /*! @copydoc try_get */ - template - inline auto try_get() ENTT_NOEXCEPT { - if constexpr(sizeof...(Component) == 1) { - return (const_cast(std::as_const(*this).template try_get()), ...); - } else { - return std::tuple{try_get()...}; - } - } +template +struct has_tuple_size_value::value)>>: std::true_type {}; - /** - * @brief Creates a new entity using a given prototype. - * - * Utility shortcut, equivalent to the following snippet: - * - * @code{.cpp} - * const auto entity = registry.create(); - * prototype(registry, entity); - * @endcode - * - * @note - * The registry may or may not be different from the one already used by - * the prototype. There is also an overload that directly uses the - * underlying registry. - * - * @param other A valid reference to a registry. - * @return A valid entity identifier. - */ - entity_type create(registry_type &other) const { - const auto entt = other.create(); - assign(other, entt); - return entt; - } +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} - /** - * @brief Creates a new entity using a given prototype. - * - * Utility shortcut, equivalent to the following snippet: - * - * @code{.cpp} - * const auto entity = registry.create(); - * prototype(entity); - * @endcode - * - * @note - * This overload directly uses the underlying registry as a working space. - * Therefore, the components of the prototype and of the entity will share - * the same registry. - * - * @return A valid entity identifier. - */ - inline entity_type create() const { - return create(*reg); - } +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} - /** - * @brief Assigns the components of a prototype to a given entity. - * - * Assigning a prototype to an entity won't overwrite existing components - * under any circumstances.
- * In other words, only those components that the entity doesn't own yet are - * copied over. All the other components remain unchanged. - * - * @note - * The registry may or may not be different from the one already used by - * the prototype. There is also an overload that directly uses the - * underlying registry. - * - * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. - * - * @param other A valid reference to a registry. - * @param dst A valid entity identifier. - */ - void assign(registry_type &other, const entity_type dst) const { - for(auto &handler: handlers) { - handler.second.assign(*this, other, dst); - } +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; } +} - /** - * @brief Assigns the components of a prototype to a given entity. - * - * Assigning a prototype to an entity won't overwrite existing components - * under any circumstances.
- * In other words, only those components that the entity doesn't own yet are - * copied over. All the other components remain unchanged. - * - * @note - * This overload directly uses the underlying registry as a working space. - * Therefore, the components of the prototype and of the entity will share - * the same registry. - * - * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. - * - * @param dst A valid entity identifier. - */ - inline void assign(const entity_type dst) const { - assign(*reg, dst); +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); } +} - /** - * @brief Assigns or replaces the components of a prototype for an entity. - * - * Existing components are overwritten, if any. All the other components - * will be copied over to the target entity. - * - * @note - * The registry may or may not be different from the one already used by - * the prototype. There is also an overload that directly uses the - * underlying registry. - * - * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. - * - * @param other A valid reference to a registry. - * @param dst A valid entity identifier. - */ - void assign_or_replace(registry_type &other, const entity_type dst) const { - for(auto &handler: handlers) { - handler.second.assign_or_replace(*this, other, dst); - } - } +} // namespace internal - /** - * @brief Assigns or replaces the components of a prototype for an entity. - * - * Existing components are overwritten, if any. All the other components - * will be copied over to the target entity. - * - * @note - * This overload directly uses the underlying registry as a working space. - * Therefore, the components of the prototype and of the entity will share - * the same registry. - * - * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. - * - * @param dst A valid entity identifier. - */ - inline void assign_or_replace(const entity_type dst) const { - assign_or_replace(*reg, dst); - } +/** + * Internal details not to be documented. + * @endcond + */ - /** - * @brief Assigns the components of a prototype to an entity. - * - * Assigning a prototype to an entity won't overwrite existing components - * under any circumstances.
- * In other words, only the components that the entity doesn't own yet are - * copied over. All the other components remain unchanged. - * - * @note - * The registry may or may not be different from the one already used by - * the prototype. There is also an overload that directly uses the - * underlying registry. - * - * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. - * - * @param other A valid reference to a registry. - * @param dst A valid entity identifier. - */ - inline void operator()(registry_type &other, const entity_type dst) const ENTT_NOEXCEPT { - assign(other, dst); - } +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; - /** - * @brief Assigns the components of a prototype to an entity. - * - * Assigning a prototype to an entity won't overwrite existing components - * under any circumstances.
- * In other words, only the components that the entity doesn't own yet are - * copied over. All the other components remain unchanged. - * - * @note - * This overload directly uses the underlying registry as a working space. - * Therefore, the components of the prototype and of the entity will share - * the same registry. - * - * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. - * - * @param dst A valid entity identifier. - */ - inline void operator()(const entity_type dst) const ENTT_NOEXCEPT { - assign(*reg, dst); - } +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_equality_comparable_v = is_equality_comparable::value; - /** - * @brief Creates a new entity using a given prototype. - * - * Utility shortcut, equivalent to the following snippet: - * - * @code{.cpp} - * const auto entity = registry.create(); - * prototype(registry, entity); - * @endcode - * - * @note - * The registry may or may not be different from the one already used by - * the prototype. There is also an overload that directly uses the - * underlying registry. - * - * @param other A valid reference to a registry. - * @return A valid entity identifier. - */ - inline entity_type operator()(registry_type &other) const ENTT_NOEXCEPT { - return create(other); - } +/** + * @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 = std::remove_const_t; +}; - /** - * @brief Creates a new entity using a given prototype. - * - * Utility shortcut, equivalent to the following snippet: - * - * @code{.cpp} - * const auto entity = registry.create(); - * prototype(entity); - * @endcode - * - * @note - * This overload directly uses the underlying registry as a working space. - * Therefore, the components of the prototype and of the entity will share - * the same registry. - * - * @return A valid entity identifier. - */ - inline entity_type operator()() const ENTT_NOEXCEPT { - return create(*reg); - } +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; - /** - * @brief Returns a reference to the underlying registry. - * @return A reference to the underlying registry. - */ - inline const registry_type & backend() const ENTT_NOEXCEPT { - return *reg; - } +/** + * @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 = typename constness_as::type; - /*! @copydoc backend */ - inline registry_type & backend() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).backend()); - } +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); -private: - std::unordered_map handlers; - registry_type *reg; - entity_type entity; + 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 = std::remove_pointer_t()))>; }; +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; -} +} // namespace entt +#endif -#endif // ENTT_ENTITY_PROTOTYPE_HPP +// #include "fwd.hpp" +#ifndef ENTT_SIGNAL_FWD_HPP +#define ENTT_SIGNAL_FWD_HPP -// #include "entity/registry.hpp" +#include -// #include "entity/runtime_view.hpp" +namespace entt { -// #include "entity/snapshot.hpp" +template +class delegate; -// #include "entity/sparse_set.hpp" +template> +class basic_dispatcher; -// #include "entity/storage.hpp" +template +class emitter; -// #include "entity/view.hpp" +class connection; -// #include "locator/locator.hpp" -#ifndef ENTT_LOCATOR_LOCATOR_HPP -#define ENTT_LOCATOR_LOCATOR_HPP +struct scoped_connection; +template +class sink; -#include -#include -// #include "../config/config.h" -#ifndef ENTT_CONFIG_CONFIG_H -#define ENTT_CONFIG_CONFIG_H +template> +class sigh; +/*! @brief Alias declaration for the most common use case. */ +using dispatcher = basic_dispatcher<>; -#ifndef ENTT_NOEXCEPT -#define ENTT_NOEXCEPT noexcept -#endif // ENTT_NOEXCEPT +} // namespace entt +#endif -#ifndef ENTT_HS_SUFFIX -#define ENTT_HS_SUFFIX _hs -#endif // ENTT_HS_SUFFIX +namespace entt { -#ifndef ENTT_NO_ATOMIC -#include -template -using maybe_atomic_t = std::atomic; -#else // ENTT_NO_ATOMIC -template -using maybe_atomic_t = Type; -#endif // ENTT_NO_ATOMIC +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ +namespace internal { -#ifndef ENTT_ID_TYPE -#include -#define ENTT_ID_TYPE std::uint32_t -#endif // ENTT_ID_TYPE +template +auto function_pointer(Ret (*)(Args...)) -> Ret (*)(Args...); +template +auto function_pointer(Ret (*)(Type, Args...), Other &&) -> Ret (*)(Args...); -#ifndef ENTT_PAGE_SIZE -#define ENTT_PAGE_SIZE 32768 -#endif // ENTT_PAGE_SIZE +template +auto function_pointer(Ret (Class::*)(Args...), Other &&...) -> Ret (*)(Args...); +template +auto function_pointer(Ret (Class::*)(Args...) const, Other &&...) -> Ret (*)(Args...); -#ifndef ENTT_DISABLE_ASSERT -#include -#define ENTT_ASSERT(condition) assert(condition) -#else // ENTT_DISABLE_ASSERT -#define ENTT_ASSERT(...) ((void)0) -#endif // ENTT_DISABLE_ASSERT +template +auto function_pointer(Type Class::*, Other &&...) -> Type (*)(); +template +using function_pointer_t = decltype(internal::function_pointer(std::declval()...)); -#endif // ENTT_CONFIG_CONFIG_H +template +[[nodiscard]] constexpr auto index_sequence_for(Ret (*)(Args...)) { + return std::index_sequence_for{}; +} +} // namespace internal +/** + * Internal details not to be documented. + * @endcond + */ -namespace entt { +/*! @brief Used to wrap a function or a member of a specified type. */ +template +struct connect_arg_t {}; +/*! @brief Constant of type connect_arg_t used to disambiguate calls. */ +template +inline constexpr connect_arg_t connect_arg{}; /** - * @brief Service locator, nothing more. + * @brief Basic delegate implementation. * - * A service locator can be 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 template - * based implementation tries to fill the gap and to get rid of the burden of - * defining a different specific locator for each application. + * 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 Service Type of service managed by the locator. + * @tparam Ret Return type of a function type. + * @tparam Args Types of arguments of a function type. */ -template -struct service_locator { - /*! @brief Type of service offered. */ - using service_type = Service; +template +class delegate { + template + [[nodiscard]] auto wrap(std::index_sequence) ENTT_NOEXCEPT { + return [](const void *, Args... args) -> Ret { + [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); + return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); + }; + } - /*! @brief Default constructor, deleted on purpose. */ - service_locator() = delete; - /*! @brief Default destructor, deleted on purpose. */ - ~service_locator() = delete; + template + [[nodiscard]] auto wrap(Type &, std::index_sequence) ENTT_NOEXCEPT { + return [](const void *payload, Args... args) -> Ret { + [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); + Type *curr = static_cast(const_cast *>(payload)); + return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); + }; + } + + template + [[nodiscard]] auto wrap(Type *, std::index_sequence) ENTT_NOEXCEPT { + return [](const void *payload, Args... args) -> Ret { + [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); + Type *curr = static_cast(const_cast *>(payload)); + return static_cast(std::invoke(Candidate, curr, std::forward>>(std::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() ENTT_NOEXCEPT + : instance{nullptr}, + fn{nullptr} {} + + /** + * @brief Constructs a delegate and connects a free function or an unbound + * member. + * @tparam Candidate Function or member to connect to the delegate. + */ + template + delegate(connect_arg_t) ENTT_NOEXCEPT { + connect(); + } + + /** + * @brief Constructs a delegate and connects a free function with payload or + * a bound member. + * @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 object that fits the purpose. + */ + template + delegate(connect_arg_t, Type &&value_or_instance) ENTT_NOEXCEPT { + connect(std::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) ENTT_NOEXCEPT { + connect(function, payload); + } /** - * @brief Tests if a valid service implementation is set. - * @return True if the service is set, false otherwise. + * @brief Connects a free function or an unbound member to a delegate. + * @tparam Candidate Function or member to connect to the delegate. */ - inline static bool empty() ENTT_NOEXCEPT { - return !static_cast(service); + template + void connect() ENTT_NOEXCEPT { + instance = nullptr; + + if constexpr(std::is_invocable_r_v) { + fn = [](const void *, Args... args) -> Ret { + return Ret(std::invoke(Candidate, std::forward(args)...)); + }; + } else if constexpr(std::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 Returns a weak pointer to a service implementation, if any. + * @brief Connects a free function with payload or a bound member to a + * delegate. * - * Clients of a service shouldn't retain references to it. The recommended - * way is to retrieve the service implementation currently set each and - * every time the need of using it arises. Otherwise users can incur in - * unexpected behaviors. + * 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. * - * @return A reference to the service implementation currently set, if any. + * @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. */ - inline static std::weak_ptr get() ENTT_NOEXCEPT { - return service; + template + void connect(Type &value_or_instance) ENTT_NOEXCEPT { + instance = &value_or_instance; + + if constexpr(std::is_invocable_r_v) { + fn = [](const void *payload, Args... args) -> Ret { + Type *curr = static_cast(const_cast *>(payload)); + return Ret(std::invoke(Candidate, *curr, std::forward(args)...)); + }; + } else { + fn = wrap(value_or_instance, internal::index_sequence_for(internal::function_pointer_t{})); + } } /** - * @brief Returns a weak reference to a service implementation, if any. + * @brief Connects a free function with payload or a bound member to a + * delegate. * - * Clients of a service shouldn't retain references to it. The recommended - * way is to retrieve the service implementation currently set each and - * every time the need of using it arises. Otherwise users can incur in - * unexpected behaviors. + * @sa connect(Type &) * - * @warning - * In case no service implementation has been set, a call to this function - * results in undefined behavior. + * @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) ENTT_NOEXCEPT { + instance = value_or_instance; + + if constexpr(std::is_invocable_r_v) { + fn = [](const void *payload, Args... args) -> Ret { + Type *curr = static_cast(const_cast *>(payload)); + return Ret(std::invoke(Candidate, curr, std::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. * - * @return A reference to the service implementation currently set, if any. + * @param function Function to connect to the delegate. + * @param payload User defined arbitrary data. */ - inline static Service & ref() ENTT_NOEXCEPT { - return *service; + void connect(function_type *function, const void *payload = nullptr) ENTT_NOEXCEPT { + instance = payload; + fn = function; } /** - * @brief Sets or replaces a service. - * @tparam Impl Type of the new service to use. - * @tparam Args Types of arguments to use to construct the service. - * @param args Parameters to use to construct the service. + * @brief Resets a delegate. + * + * After a reset, a delegate cannot be invoked anymore. */ - template - inline static void set(Args &&... args) { - service = std::make_shared(std::forward(args)...); + void reset() ENTT_NOEXCEPT { + instance = nullptr; + fn = nullptr; } /** - * @brief Sets or replaces a service. - * @param ptr Service to use to replace the current one. + * @brief Returns the instance or the payload linked to a delegate, if any. + * @return An opaque pointer to the underlying data. */ - inline static void set(std::shared_ptr ptr) { - ENTT_ASSERT(static_cast(ptr)); - service = std::move(ptr); + [[nodiscard]] const void *data() const ENTT_NOEXCEPT { + return instance; } /** - * @brief Resets a service. + * @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. * - * The service is no longer valid after a reset. + * @param args Arguments to use to invoke the underlying function. + * @return The value returned by the underlying function. */ - inline static void reset() { - service.reset(); + Ret operator()(Args... args) const { + ENTT_ASSERT(static_cast(*this), "Uninitialized delegate"); + return fn(instance, std::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 ENTT_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 ENTT_NOEXCEPT { + return fn == other.fn && instance == other.instance; } private: - inline static std::shared_ptr service = nullptr; + const void *instance; + function_type *fn; }; - +/** + * @brief Compares the contents of two delegates. + * @tparam Ret Return type of a function type. + * @tparam Args Types of arguments of a function type. + * @param lhs A valid delegate object. + * @param rhs A valid delegate object. + * @return True if the two contents differ, false otherwise. + */ +template +[[nodiscard]] bool operator!=(const delegate &lhs, const delegate &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); } +/** + * @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; -#endif // ENTT_LOCATOR_LOCATOR_HPP +} // namespace entt -// #include "meta/factory.hpp" -#ifndef ENTT_META_FACTORY_HPP -#define ENTT_META_FACTORY_HPP +#endif +// #include "signal/dispatcher.hpp" +#ifndef ENTT_SIGNAL_DISPATCHER_HPP +#define ENTT_SIGNAL_DISPATCHER_HPP +#include +#include +#include +#include #include +#include +// #include "../config/config.h" + +// #include "../container/dense_map.hpp" +#ifndef ENTT_CONTAINER_DENSE_MAP_HPP +#define ENTT_CONTAINER_DENSE_MAP_HPP + #include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include // #include "../config/config.h" #ifndef ENTT_CONFIG_CONFIG_H #define ENTT_CONFIG_CONFIG_H +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H -#ifndef ENTT_NOEXCEPT -#define ENTT_NOEXCEPT noexcept -#endif // ENTT_NOEXCEPT - - -#ifndef ENTT_HS_SUFFIX -#define ENTT_HS_SUFFIX _hs -#endif // ENTT_HS_SUFFIX - - -#ifndef ENTT_NO_ATOMIC -#include -template -using maybe_atomic_t = std::atomic; -#else // ENTT_NO_ATOMIC -template -using maybe_atomic_t = Type; -#endif // ENTT_NO_ATOMIC +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) -#ifndef ENTT_ID_TYPE -#include -#define ENTT_ID_TYPE std::uint32_t -#endif // ENTT_ID_TYPE +#endif -#ifndef ENTT_PAGE_SIZE -#define ENTT_PAGE_SIZE 32768 -#endif // ENTT_PAGE_SIZE +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) -#ifndef ENTT_DISABLE_ASSERT -#include -#define ENTT_ASSERT(condition) assert(condition) -#else // ENTT_DISABLE_ASSERT -#define ENTT_ASSERT(...) ((void)0) -#endif // ENTT_DISABLE_ASSERT +#endif -#endif // ENTT_CONFIG_CONFIG_H +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 -// #include "../core/hashed_string.hpp" -#ifndef ENTT_CORE_HASHED_STRING_HPP -#define ENTT_CORE_HASHED_STRING_HPP +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "../core/compressed_pair.hpp" +#ifndef ENTT_CORE_COMPRESSED_PAIR_HPP +#define ENTT_CORE_COMPRESSED_PAIR_HPP #include +#include +#include +#include // #include "../config/config.h" #ifndef ENTT_CONFIG_CONFIG_H #define ENTT_CONFIG_CONFIG_H +// #include "version.h" +#ifndef ENTT_CONFIG_VERSION_H +#define ENTT_CONFIG_VERSION_H -#ifndef ENTT_NOEXCEPT -#define ENTT_NOEXCEPT noexcept -#endif // ENTT_NOEXCEPT +// #include "macro.h" +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) -#ifndef ENTT_HS_SUFFIX -#define ENTT_HS_SUFFIX _hs -#endif // ENTT_HS_SUFFIX +#endif -#ifndef ENTT_NO_ATOMIC -#include -template -using maybe_atomic_t = std::atomic; -#else // ENTT_NO_ATOMIC -template -using maybe_atomic_t = Type; -#endif // ENTT_NO_ATOMIC +#define ENTT_VERSION_MAJOR 3 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) -#ifndef ENTT_ID_TYPE -#include -#define ENTT_ID_TYPE std::uint32_t -#endif // ENTT_ID_TYPE +#endif -#ifndef ENTT_PAGE_SIZE -#define ENTT_PAGE_SIZE 32768 -#endif // ENTT_PAGE_SIZE +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif + +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif -#ifndef ENTT_DISABLE_ASSERT -#include -#define ENTT_ASSERT(condition) assert(condition) -#else // ENTT_DISABLE_ASSERT -#define ENTT_ASSERT(...) ((void)0) -#endif // ENTT_DISABLE_ASSERT +#ifndef ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#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(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif + +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#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 + +#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 + +#endif + +// #include "type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP +#include +#include +#include +#include +// #include "../config/config.h" -#endif // ENTT_CONFIG_CONFIG_H +// #include "fwd.hpp" +#ifndef ENTT_CORE_FWD_HPP +#define ENTT_CORE_FWD_HPP +#include +#include +// #include "../config/config.h" namespace entt { +template)> +class basic_any; -/** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. - */ - +/*! @brief Alias declaration for type identifiers. */ +using id_type = ENTT_ID_TYPE; -namespace internal { +/*! @brief Alias declaration for the most common use case. */ +using any = basic_any<>; +} // namespace entt -template -struct fnv1a_traits; +#endif -template<> -struct fnv1a_traits { - static constexpr std::uint32_t offset = 2166136261; - static constexpr std::uint32_t prime = 16777619; -}; +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 TURN_OFF_DOXYGEN */ choice_t /*! @endcond */ +{}; +/*! @copybrief choice_t */ template<> -struct fnv1a_traits { - static constexpr std::uint64_t offset = 14695981039346656037ull; - static constexpr std::uint64_t prime = 1099511628211ull; -}; - - -} - +struct choice_t<0> {}; /** - * Internal details not to be documented. - * @endcond TURN_OFF_DOXYGEN + * @brief Variable template for the choice trick. + * @tparam N Number of choices available. */ - +template +inline constexpr choice_t choice{}; /** - * @brief Zero overhead unique identifier. + * @brief Identity type trait. * - * A hashed string is a compile-time tool that allows users to use - * human-readable identifers 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. + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. */ -class hashed_string { - using traits_type = internal::fnv1a_traits; - - struct const_wrapper { - // non-explicit constructor on purpose - constexpr const_wrapper(const char *curr) ENTT_NOEXCEPT: str{curr} {} - const char *str; - }; - - // Fowler–Noll–Vo hash function v. 1a - the good - inline static constexpr ENTT_ID_TYPE helper(ENTT_ID_TYPE partial, const char *curr) ENTT_NOEXCEPT { - return curr[0] == 0 ? partial : helper((partial^curr[0])*traits_type::prime, curr+1); - } - -public: - /*! @brief Unsigned integer type. */ - using hash_type = ENTT_ID_TYPE; +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; - /** - * @brief Returns directly the numeric representation of a string. - * - * Forcing template resolution avoids implicit conversions. An - * human-readable identifier can be anything but a plain, old bunch of - * characters.
- * Example of use: - * @code{.cpp} - * const auto value = hashed_string::to_value("my.png"); - * @endcode - * - * @tparam N Number of characters of the identifier. - * @param str Human-readable identifer. - * @return The numeric representation of the string. - */ - template - inline static constexpr hash_type to_value(const char (&str)[N]) ENTT_NOEXCEPT { - return helper(traits_type::offset, str); - } +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; - /** - * @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. - */ - inline static hash_type to_value(const_wrapper wrapper) ENTT_NOEXCEPT { - return helper(traits_type::offset, wrapper.str); - } +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; - /** - * @brief Returns directly the numeric representation of a string view. - * @param str Human-readable identifer. - * @param size Length of the string to hash. - * @return The numeric representation of the string. - */ - inline static hash_type to_value(const char *str, std::size_t size) ENTT_NOEXCEPT { - ENTT_ID_TYPE partial{traits_type::offset}; - while(size--) { partial = (partial^(str++)[0])*traits_type::prime; } - return partial; - } +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; - /*! @brief Constructs an empty hashed string. */ - constexpr hashed_string() ENTT_NOEXCEPT - : str{nullptr}, hash{} - {} +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::size_t size_of_v = size_of::value; - /** - * @brief Constructs a hashed string from an array of const chars. - * - * Forcing template resolution avoids implicit conversions. An - * human-readable identifier can be anything but a plain, old bunch of - * characters.
- * Example of use: - * @code{.cpp} - * hashed_string hs{"my.png"}; - * @endcode - * - * @tparam N Number of characters of the identifier. - * @param curr Human-readable identifer. - */ - template - constexpr hashed_string(const char (&curr)[N]) ENTT_NOEXCEPT - : str{curr}, hash{helper(traits_type::offset, curr)} - {} +/** + * @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 Explicit constructor on purpose to avoid constructing a hashed - * string directly from a `const char *`. - * @param wrapper Helps achieving the purpose by relying on overloading. - */ - explicit constexpr hashed_string(const_wrapper wrapper) ENTT_NOEXCEPT - : str{wrapper.str}, hash{helper(traits_type::offset, wrapper.str)} - {} +/** + * @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 Returns the human-readable representation of a hashed string. - * @return The string used to initialize the instance. - */ - constexpr const char * data() const ENTT_NOEXCEPT { - return str; - } +/** + * @brief Wraps a static constant. + * @tparam Value A static constant. + */ +template +using integral_constant = std::integral_constant; - /** - * @brief Returns the numeric representation of a hashed string. - * @return The numeric representation of the instance. - */ - constexpr hash_type value() const ENTT_NOEXCEPT { - return hash; - } +/** + * @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 Returns the human-readable representation of a hashed string. - * @return The string used to initialize the instance. - */ - constexpr operator const char *() const ENTT_NOEXCEPT { return str; } +/** + * @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); +}; - /*! @copydoc value */ - constexpr operator hash_type() const ENTT_NOEXCEPT { return hash; } +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_element; - /** - * @brief Compares two hashed strings. - * @param other Hashed string with which to compare. - * @return True if the two hashed strings are identical, false otherwise. - */ - constexpr bool operator==(const hashed_string &other) const ENTT_NOEXCEPT { - return hash == other.hash; - } +/** + * @brief Provides compile-time indexed access to the types of a type list. + * @tparam Index Index of the type to return. + * @tparam Type First type provided by the type list. + * @tparam Other Other types provided by the type list. + */ +template +struct type_list_element> + : type_list_element> {}; -private: - const char *str; - hash_type hash; +/** + * @brief Provides compile-time indexed access to the types of a type list. + * @tparam Type 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 = Type; }; +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; /** - * @brief Compares two hashed strings. - * @param lhs A valid hashed string. - * @param rhs A valid hashed string. - * @return True if the two hashed strings are identical, false otherwise. + * @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. */ -constexpr bool operator!=(const hashed_string &lhs, const hashed_string &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); +template +constexpr 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 User defined literal for hashed strings. - * @param str The literal without its suffix. - * @return A properly initialized hashed string. + * @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. */ -constexpr entt::hashed_string operator"" ENTT_HS_SUFFIX(const char *str, std::size_t) ENTT_NOEXCEPT { - return entt::hashed_string{str}; -} - +template +struct type_list_cat, type_list, List...> { + /*! @brief A type list composed by the types of all the type lists. */ + using type = typename type_list_cat, List...>::type; +}; -#endif // ENTT_CORE_HASHED_STRING_HPP +/** + * @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; +}; -// #include "meta.hpp" -#ifndef ENTT_META_META_HPP -#define ENTT_META_META_HPP +/** + * @brief Helper type. + * @tparam List Type lists to concatenate. + */ +template +using type_list_cat_t = typename type_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; -#include -#include -#include -#include -#include -#include -#include -#include -// #include "../config/config.h" +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; +}; -// #include "../core/hashed_string.hpp" +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; +/** + * @brief Helper type. + * @tparam Type A type list. + */ +template +using type_list_unique_t = typename type_list_unique::type; +/** + * @brief Provides the member constant `value` 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; -namespace entt { +/** + * @copybrief type_list_contains + * @tparam Type Types provided by the type list. + * @tparam Other Type to look for. + */ +template +struct type_list_contains, Other>: std::disjunction...> {}; +/** + * @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; -class meta_any; -class meta_handle; -class meta_prop; -class meta_base; -class meta_conv; -class meta_ctor; -class meta_dtor; -class meta_data; -class meta_func; -class meta_type; +/*! @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>...>; +}; /** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. + * @brief Helper type. + * @tparam List Type lists between which to compute the difference. */ +template +using type_list_diff_t = typename type_list_diff::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); +}; -namespace internal { - - -struct meta_type_node; +/*! @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> {}; -struct meta_prop_node { - meta_prop_node * next; - meta_any(* const key)(); - meta_any(* const value)(); - meta_prop(* const meta)(); +/** + * @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 value. */ + static constexpr auto value = Value; }; +/** + * @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; -struct meta_base_node { - meta_base_node ** const underlying; - meta_type_node * const parent; - meta_base_node * next; - meta_type_node *(* const type)(); - void *(* const cast)(void *); - meta_base(* const meta)(); -}; +/** + * @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 +constexpr value_list operator+(value_list, value_list) { + return {}; +} +/*! @brief Primary template isn't defined on purpose. */ +template +struct value_list_cat; -struct meta_conv_node { - meta_conv_node ** const underlying; - meta_type_node * const parent; - meta_conv_node * next; - meta_type_node *(* const type)(); - meta_any(* const conv)(void *); - meta_conv(* const meta)(); +/*! @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<>; }; - -struct meta_ctor_node { - using size_type = std::size_t; - meta_ctor_node ** const underlying; - meta_type_node * const parent; - meta_ctor_node * next; - meta_prop_node * prop; - const size_type size; - meta_type_node *(* const arg)(size_type); - meta_any(* const invoke)(meta_any * const); - meta_ctor(* const meta)(); +/** + * @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 = typename value_list_cat, List...>::type; }; - -struct meta_dtor_node { - meta_dtor_node ** const underlying; - meta_type_node * const parent; - bool(* const invoke)(meta_handle); - meta_dtor(* const meta)(); +/** + * @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 = typename value_list_cat::type; -struct meta_data_node { - meta_data_node ** const underlying; - hashed_string name; - meta_type_node * const parent; - meta_data_node * next; - meta_prop_node * prop; - const bool is_const; - const bool is_static; - meta_type_node *(* const type)(); - bool(* const set)(meta_handle, meta_any, meta_any); - meta_any(* const get)(meta_handle, meta_any); - meta_data(* const meta)(); -}; +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::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>: std::is_invocable {}; -struct meta_func_node { - using size_type = std::size_t; - meta_func_node ** const underlying; - hashed_string name; - meta_type_node * const parent; - meta_func_node * next; - meta_prop_node * prop; - const size_type size; - const bool is_const; - const bool is_static; - meta_type_node *(* const ret)(); - meta_type_node *(* const arg)(size_type); - meta_any(* const invoke)(meta_handle, meta_any *); - meta_func(* const meta)(); -}; +/** + * @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>: std::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; -struct meta_type_node { - using size_type = std::size_t; - hashed_string name; - meta_type_node * next; - meta_prop_node * prop; - const bool is_void; - const bool is_integral; - const bool is_floating_point; - const bool is_array; - const bool is_enum; - const bool is_union; - const bool is_class; - const bool is_pointer; - const bool is_function; - const bool is_member_object_pointer; - const bool is_member_function_pointer; - const size_type extent; - meta_type(* const remove_pointer)(); - bool(* const destroy)(meta_handle); - meta_type(* const meta)(); - meta_base_node *base; - meta_conv_node *conv; - meta_ctor_node *ctor; - meta_dtor_node *dtor; - meta_data_node *data; - meta_func_node *func; -}; +/*! @brief Same as std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::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>: std::is_invocable_r {}; -template -struct meta_node { - inline static meta_type_node *type = nullptr; -}; +/** + * @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` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; +/*! @copydoc is_complete */ template -struct meta_node { - inline static meta_type_node *type = nullptr; - - template - inline static meta_base_node *base = nullptr; +struct is_complete>: std::true_type {}; - template - inline static meta_conv_node *conv = nullptr; +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_complete_v = is_complete::value; - template - inline static meta_ctor_node *ctor = nullptr; +/** + * @brief Provides the member constant `value` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; - template - inline static meta_dtor_node *dtor = nullptr; +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ - template - inline static meta_data_node *data = nullptr; +namespace internal { - template - inline static meta_func_node *func = nullptr; +template +struct has_iterator_category: std::false_type {}; - inline static meta_type_node * resolve() ENTT_NOEXCEPT; -}; +template +struct has_iterator_category::iterator_category>>: std::true_type {}; +} // namespace internal -template -struct meta_info: meta_node>...> {}; +/** + * Internal details not to be documented. + * @endcond + */ +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : internal::has_iterator_category {}; -template -void iterate(Op op, const Node *curr) ENTT_NOEXCEPT { - while(curr) { - op(curr); - curr = curr->next; - } -} +/** + * @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` 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 + : std::conjunction, std::negation>> {}; -template -void iterate(Op op, const meta_type_node *node) ENTT_NOEXCEPT { - if(node) { - auto *curr = node->base; - iterate(op, node->*Member); +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_ebco_eligible_v = is_ebco_eligible::value; - while(curr) { - iterate(op, curr->type()); - curr = curr->next; - } - } -} +/** + * @brief Provides the member constant `value` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; -template -auto find_if(Op op, const Node *curr) ENTT_NOEXCEPT { - while(curr && !op(curr)) { - curr = curr->next; - } +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; - return curr; -} +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ -template -auto find_if(Op op, const meta_type_node *node) ENTT_NOEXCEPT --> decltype(find_if(op, node->*Member)) { - decltype(find_if(op, node->*Member)) ret = nullptr; +namespace internal { - if(node) { - ret = find_if(op, node->*Member); - auto *curr = node->base; +template +struct has_tuple_size_value: std::false_type {}; - while(curr && !ret) { - ret = find_if(op, curr->type()); - curr = curr->next; - } - } +template +struct has_tuple_size_value::value)>>: std::true_type {}; - return ret; +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); } +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} template -const Type * try_cast(const meta_type_node *node, void *instance) ENTT_NOEXCEPT { - const auto *type = meta_info::resolve(); - void *ret = nullptr; - - if(node == type) { - ret = instance; +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); } else { - const auto *base = find_if<&meta_type_node::base>([type](auto *candidate) { - return candidate->type() == type; - }, node); - - ret = base ? base->cast(instance) : nullptr; + return is_equality_comparable::value; } - - return static_cast(ret); -} - - -template -inline bool can_cast_or_convert(const meta_type_node *from, const meta_type_node *to) ENTT_NOEXCEPT { - return (from == to) || find_if([to](auto *node) { - return node->type() == to; - }, from); -} - - -template -inline auto ctor(std::index_sequence, const meta_type_node *node) ENTT_NOEXCEPT { - return internal::find_if([](auto *candidate) { - return candidate->size == sizeof...(Args) && - (([](auto *from, auto *to) { - return internal::can_cast_or_convert<&internal::meta_type_node::base>(from, to) - || internal::can_cast_or_convert<&internal::meta_type_node::conv>(from, to); - }(internal::meta_info::resolve(), candidate->arg(Indexes))) && ...); - }, node->ctor); } - +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); + } } +} // namespace internal /** * Internal details not to be documented. - * @endcond TURN_OFF_DOXYGEN + * @endcond */ +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; /** - * @brief Meta any object. - * - * A meta any is an opaque container for single values of any type. - * - * This class uses a technique called small buffer optimization (SBO) to - * completely eliminate the need to allocate memory, where possible.
- * From the user's point of view, nothing will change, but the elimination of - * allocations will reduce the jumps in memory and therefore will avoid chasing - * of pointers. This will greatly improve the use of the cache, thus increasing - * the overall performance. + * @brief Helper variable template. + * @tparam Type The type to test. */ -class meta_any { - /*! @brief A meta handle is allowed to _inherit_ from a meta any. */ - friend class meta_handle; - - using storage_type = std::aligned_storage_t; - using compare_fn_type = bool(*)(const void *, const void *); - using copy_fn_type = void *(*)(storage_type &, const void *); - using destroy_fn_type = void(*)(storage_type &); +template +inline constexpr bool is_equality_comparable_v = is_equality_comparable::value; - template - inline static auto compare(int, const Type &lhs, const Type &rhs) - -> decltype(lhs == rhs, bool{}) { - return lhs == rhs; - } +/** + * @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 = std::remove_const_t; +}; - template - inline static bool compare(char, const Type &lhs, const Type &rhs) { - return &lhs == &rhs; - } +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; - template - static bool compare(const void *lhs, const void *rhs) { - return compare(0, *static_cast(lhs), *static_cast(rhs)); - } +/** + * @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 = typename constness_as::type; - template - static void * copy_storage(storage_type &storage, const void *instance) { - return new (&storage) Type{*static_cast(instance)}; - } +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); - template - static void * copy_object(storage_type &storage, const void *instance) { - using chunk_type = std::aligned_storage_t; - auto *chunk = new chunk_type; - new (&storage) chunk_type *{chunk}; - return new (chunk) Type{*static_cast(instance)}; - } + template + static Class *clazz(Ret (Class::*)(Args...)); - template - static void destroy_storage(storage_type &storage) { - auto *node = internal::meta_info::resolve(); - auto *instance = reinterpret_cast(&storage); - node->dtor ? node->dtor->invoke(*instance) : node->destroy(*instance); - } + template + static Class *clazz(Ret (Class::*)(Args...) const); - template - static void destroy_object(storage_type &storage) { - using chunk_type = std::aligned_storage_t; - auto *node = internal::meta_info::resolve(); - auto *chunk = *reinterpret_cast(&storage); - auto *instance = reinterpret_cast(chunk); - node->dtor ? node->dtor->invoke(*instance) : node->destroy(*instance); - delete chunk; - } + template + static Class *clazz(Type Class::*); public: - /*! @brief Default constructor. */ - meta_any() ENTT_NOEXCEPT - : storage{}, - instance{nullptr}, - node{nullptr}, - destroy_fn{nullptr}, - compare_fn{nullptr}, - copy_fn{nullptr} - {} - - /** - * @brief Constructs a meta any from a given value. - * - * This class uses a technique called small buffer optimization (SBO) to - * completely eliminate the need to allocate memory, where possible.
- * From the user's point of view, nothing will change, but the elimination - * of allocations will reduce the jumps in memory and therefore will avoid - * chasing of pointers. This will greatly improve the use of the cache, thus - * increasing the overall performance. - * - * @tparam Type Type of object to use to initialize the container. - * @param type An instance of an object to use to initialize the container. - */ - template>, meta_any>>> - meta_any(Type &&type) { - using actual_type = std::remove_cv_t>; - node = internal::meta_info::resolve(); - - compare_fn = &compare; - - if constexpr(sizeof(actual_type) <= sizeof(void *)) { - instance = new (&storage) actual_type{std::forward(type)}; - destroy_fn = &destroy_storage; - copy_fn = ©_storage; - } else { - using chunk_type = std::aligned_storage_t; - - auto *chunk = new chunk_type; - instance = new (chunk) actual_type{std::forward(type)}; - new (&storage) chunk_type *{chunk}; - - destroy_fn = &destroy_object; - copy_fn = ©_object; - } - } - - /** - * @brief Copy constructor. - * @param other The instance to copy from. - */ - meta_any(const meta_any &other) - : meta_any{} - { - if(other) { - instance = other.copy_fn(storage, other.instance); - node = other.node; - destroy_fn = other.destroy_fn; - compare_fn = other.compare_fn; - copy_fn = other.copy_fn; - } - } - - /** - * @brief Move constructor. - * - * After meta any move construction, instances that have been moved from - * are placed in a valid but unspecified state. It's highly discouraged to - * continue using them. - * - * @param other The instance to move from. - */ - meta_any(meta_any &&other) ENTT_NOEXCEPT - : meta_any{} - { - swap(*this, other); - } + /*! @brief The class of the given non-static member object or function. */ + using type = std::remove_pointer_t()))>; +}; - /*! @brief Frees the internal storage, whatever it means. */ - ~meta_any() { - if(destroy_fn) { - destroy_fn(storage); - } - } +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; - /** - * @brief Assignment operator. - * @param other The instance to assign. - * @return This meta any object. - */ - meta_any & operator=(meta_any other) { - swap(other, *this); - return *this; - } +} // namespace entt - /** - * @brief Returns the meta type of the underlying object. - * @return The meta type of the underlying object, if any. - */ - inline meta_type type() const ENTT_NOEXCEPT; +#endif - /** - * @brief Returns an opaque pointer to the contained instance. - * @return An opaque pointer the contained instance, if any. - */ - inline const void * data() const ENTT_NOEXCEPT { - return instance; - } - /*! @copydoc data */ - inline void * data() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).data()); - } +namespace entt { - /** - * @brief Checks if it's possible to cast an instance to a given type. - * @tparam Type Type to which to cast the instance. - * @return True if the cast is viable, false otherwise. - */ - template - inline bool can_cast() const ENTT_NOEXCEPT { - const auto *type = internal::meta_info::resolve(); - return internal::can_cast_or_convert<&internal::meta_type_node::base>(node, type); - } +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ - /** - * @brief Tries to cast an instance to a given type. - * - * The type of the instance must be such that the cast is possible. - * - * @warning - * Attempting to perform a cast that isn't viable results in undefined - * behavior.
- * An assertion will abort the execution at runtime in debug mode in case - * the cast is not feasible. - * - * @tparam Type Type to which to cast the instance. - * @return A reference to the contained instance. - */ - template - inline const Type & cast() const ENTT_NOEXCEPT { - ENTT_ASSERT(can_cast()); - return *internal::try_cast(node, instance); - } +namespace internal { - /*! @copydoc cast */ - template - inline Type & cast() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).cast()); - } +template +struct compressed_pair_element { + using reference = Type &; + using const_reference = const Type &; - /** - * @brief Checks if it's possible to convert an instance to a given type. - * @tparam Type Type to which to convert the instance. - * @return True if the conversion is viable, false otherwise. - */ - template - inline bool can_convert() const ENTT_NOEXCEPT { - const auto *type = internal::meta_info::resolve(); - return internal::can_cast_or_convert<&internal::meta_type_node::conv>(node, type); - } + template>> + compressed_pair_element() + : value{} {} - /** - * @brief Tries to convert an instance to a given type and returns it. - * @tparam Type Type to which to convert the instance. - * @return A valid meta any object if the conversion is possible, an invalid - * one otherwise. - */ - template - inline meta_any convert() const ENTT_NOEXCEPT { - const auto *type = internal::meta_info::resolve(); - meta_any any{}; + template>, compressed_pair_element>>> + compressed_pair_element(Args &&args) + : value{std::forward(args)} {} - if(node == type) { - any = *static_cast(instance); - } else { - const auto *conv = internal::find_if<&internal::meta_type_node::conv>([type](auto *other) { - return other->type() == type; - }, node); + template + compressed_pair_element(std::tuple args, std::index_sequence) + : value{std::forward(std::get(args))...} {} - if(conv) { - any = conv->conv(instance); - } - } + [[nodiscard]] reference get() ENTT_NOEXCEPT { + return value; + } - return any; + [[nodiscard]] const_reference get() const ENTT_NOEXCEPT { + return value; } - /** - * @brief Tries to convert an instance to a given type. - * @tparam Type Type to which to convert the instance. - * @return True if the conversion is possible, false otherwise. - */ - template - inline bool convert() ENTT_NOEXCEPT { - bool valid = (node == internal::meta_info::resolve()); +private: + Type value; +}; - if(!valid) { - auto any = std::as_const(*this).convert(); +template +struct compressed_pair_element>>: Type { + using reference = Type &; + using const_reference = const Type &; + using base_type = Type; - if(any) { - std::swap(any, *this); - valid = true; - } - } + template>> + compressed_pair_element() + : base_type{} {} - return valid; - } + template>, compressed_pair_element>>> + compressed_pair_element(Args &&args) + : base_type{std::forward(args)} {} - /** - * @brief Returns false if a container is empty, true otherwise. - * @return False if the container is empty, true otherwise. - */ - inline explicit operator bool() const ENTT_NOEXCEPT { - return destroy_fn; - } + template + compressed_pair_element(std::tuple args, std::index_sequence) + : base_type{std::forward(std::get(args))...} {} - /** - * @brief Checks if two containers differ in their content. - * @param other Container with which to compare. - * @return False if the two containers differ in their content, true - * otherwise. - */ - inline bool operator==(const meta_any &other) const ENTT_NOEXCEPT { - return (!instance && !other.instance) || (instance && other.instance && node == other.node && compare_fn(instance, other.instance)); + [[nodiscard]] reference get() ENTT_NOEXCEPT { + return *this; } - /** - * @brief Swaps two meta any objects. - * @param lhs A valid meta any object. - * @param rhs A valid meta any object. - */ - friend void swap(meta_any &lhs, meta_any &rhs) { - using std::swap; - - if(lhs && rhs) { - storage_type buffer; - void *tmp = lhs.copy_fn(buffer, lhs.instance); - lhs.destroy_fn(lhs.storage); - lhs.instance = rhs.copy_fn(lhs.storage, rhs.instance); - rhs.destroy_fn(rhs.storage); - rhs.instance = lhs.copy_fn(rhs.storage, tmp); - lhs.destroy_fn(buffer); - } else if(lhs) { - rhs.instance = lhs.copy_fn(rhs.storage, lhs.instance); - lhs.destroy_fn(lhs.storage); - lhs.instance = nullptr; - } else if(rhs) { - lhs.instance = rhs.copy_fn(lhs.storage, rhs.instance); - rhs.destroy_fn(rhs.storage); - rhs.instance = nullptr; - } - - std::swap(lhs.node, rhs.node); - std::swap(lhs.destroy_fn, rhs.destroy_fn); - std::swap(lhs.compare_fn, rhs.compare_fn); - std::swap(lhs.copy_fn, rhs.copy_fn); - } - -private: - storage_type storage; - void *instance; - internal::meta_type_node *node; - destroy_fn_type destroy_fn; - compare_fn_type compare_fn; - copy_fn_type copy_fn; + [[nodiscard]] const_reference get() const ENTT_NOEXCEPT { + return *this; + } }; +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ /** - * @brief Meta handle object. + * @brief A compressed pair. * - * A meta handle is an opaque pointer to an instance of any type. + * A pair that exploits the _Empty Base Class Optimization_ (or _EBCO_) to + * reduce its final size to a minimum. * - * A handle doesn't perform copies and isn't responsible for the contained - * object. It doesn't prolong the lifetime of the pointed instance. Users are - * responsible for ensuring that the target object remains alive for the entire - * interval of use of the handle. + * @tparam First The type of the first element that the pair stores. + * @tparam Second The type of the second element that the pair stores. */ -class meta_handle { - meta_handle(int, meta_any &any) ENTT_NOEXCEPT - : node{any.node}, - instance{any.instance} - {} - - template - meta_handle(char, Type &&obj) ENTT_NOEXCEPT - : node{internal::meta_info::resolve()}, - instance{&obj} - {} +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 Default constructor. */ - meta_handle() ENTT_NOEXCEPT - : node{nullptr}, - instance{nullptr} - {} + /*! @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 Constructs a meta handle from a given instance. - * @tparam Type Type of object to use to initialize the handle. - * @param obj A reference to an object to use to initialize the handle. + * @brief Default constructor, conditionally enabled. + * + * This constructor is only available when the types that the pair stores + * are both at least default constructible. + * + * @tparam Dummy Dummy template parameter used for internal purposes. */ - template>, meta_handle>>> - meta_handle(Type &&obj) ENTT_NOEXCEPT - : meta_handle{0, std::forward(obj)} - {} + template && std::is_default_constructible_v>> + constexpr compressed_pair() + : first_base{}, + second_base{} {} /** - * @brief Returns the meta type of the underlying object. - * @return The meta type of the underlying object, if any. + * @brief Copy constructor. + * @param other The instance to copy from. */ - inline meta_type type() const ENTT_NOEXCEPT; + constexpr compressed_pair(const compressed_pair &other) = default; /** - * @brief Tries to cast an instance to a given type. - * - * The type of the instance must be such that the conversion is possible. - * - * @warning - * Attempting to perform a conversion that isn't viable results in undefined - * behavior.
- * An assertion will abort the execution at runtime in debug mode in case - * the conversion is not feasible. - * - * @tparam Type Type to which to cast the instance. - * @return A pointer to the contained instance. + * @brief Move constructor. + * @param other The instance to move from. */ - template - inline const Type * try_cast() const ENTT_NOEXCEPT { - return internal::try_cast(node, instance); - } - - /*! @copydoc try_cast */ - template - inline Type * try_cast() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).try_cast()); - } + constexpr compressed_pair(compressed_pair &&other) = default; /** - * @brief Returns an opaque pointer to the contained instance. - * @return An opaque pointer the contained instance, if any. + * @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. */ - inline const void * data() const ENTT_NOEXCEPT { - return instance; - } - - /*! @copydoc data */ - inline void * data() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).data()); - } + template + constexpr compressed_pair(Arg &&arg, Other &&other) + : first_base{std::forward(arg)}, + second_base{std::forward(other)} {} /** - * @brief Returns false if a handle is empty, true otherwise. - * @return False if the handle is empty, true otherwise. + * @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. */ - inline explicit operator bool() const ENTT_NOEXCEPT { - return instance; - } - -private: - const internal::meta_type_node *node; - void *instance; -}; - - -/** - * @brief Checks if two containers differ in their content. - * @param lhs A meta any object, either empty or not. - * @param rhs A meta any object, either empty or not. - * @return True if the two containers differ in their content, false otherwise. - */ -inline bool operator!=(const meta_any &lhs, const meta_any &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); -} - + template + constexpr compressed_pair(std::piecewise_construct_t, std::tuple args, std::tuple other) + : first_base{std::move(args), std::index_sequence_for{}}, + second_base{std::move(other), std::index_sequence_for{}} {} -/** - * @brief Meta property object. - * - * A meta property is an opaque container for a key/value pair.
- * Properties are associated with any other meta object to enrich it. - */ -class meta_prop { - /*! @brief A meta factory is allowed to create meta objects. */ - template friend class meta_factory; - - inline meta_prop(const internal::meta_prop_node *curr) ENTT_NOEXCEPT - : node{curr} - {} + /** + * @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; -public: - /*! @brief Default constructor. */ - inline meta_prop() ENTT_NOEXCEPT - : node{nullptr} - {} + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This compressed pair object. + */ + constexpr compressed_pair &operator=(compressed_pair &&other) = default; /** - * @brief Returns the stored key. - * @return A meta any containing the key stored with the given property. + * @brief Returns the first element that a pair stores. + * @return The first element that a pair stores. */ - inline meta_any key() const ENTT_NOEXCEPT { - return node->key(); + [[nodiscard]] first_type &first() ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /*! @copydoc first */ + [[nodiscard]] const first_type &first() const ENTT_NOEXCEPT { + return static_cast(*this).get(); } /** - * @brief Returns the stored value. - * @return A meta any containing the value stored with the given property. + * @brief Returns the second element that a pair stores. + * @return The second element that a pair stores. */ - inline meta_any value() const ENTT_NOEXCEPT { - return node->value(); + [[nodiscard]] second_type &second() ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /*! @copydoc second */ + [[nodiscard]] const second_type &second() const ENTT_NOEXCEPT { + return static_cast(*this).get(); } /** - * @brief Returns true if a meta object is valid, false otherwise. - * @return True if the meta object is valid, false otherwise. + * @brief Swaps two compressed pair objects. + * @param other The compressed pair to swap with. */ - inline explicit operator bool() const ENTT_NOEXCEPT { - return node; + void swap(compressed_pair &other) { + using std::swap; + swap(first(), other.first()); + swap(second(), other.second()); } /** - * @brief Checks if two meta objects refer to the same node. - * @param other The meta object with which to compare. - * @return True if the two meta objects refer to the same node, false - * otherwise. + * @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. */ - inline bool operator==(const meta_prop &other) const ENTT_NOEXCEPT { - return node == other.node; + template + decltype(auto) get() ENTT_NOEXCEPT { + if constexpr(Index == 0u) { + return first(); + } else { + static_assert(Index == 1u, "Index out of bounds"); + return second(); + } } -private: - const internal::meta_prop_node *node; + /*! @copydoc get */ + template + decltype(auto) get() const ENTT_NOEXCEPT { + if constexpr(Index == 0u) { + return first(); + } else { + static_assert(Index == 1u, "Index out of bounds"); + 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, std::decay_t>; /** - * @brief Checks if two meta objects refer to the same node. - * @param lhs A meta object, either valid or not. - * @param rhs A meta object, either valid or not. - * @return True if the two meta objects refer to the same node, false otherwise. + * @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. */ -inline bool operator!=(const meta_prop &lhs, const meta_prop &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); +template +inline void swap(compressed_pair &lhs, compressed_pair &rhs) { + lhs.swap(rhs); } +} // namespace entt + +// disable structured binding support for clang 6, it messes when specializing tuple_size +#if !defined __clang_major__ || __clang_major__ > 6 +namespace std { /** - * @brief Meta base object. - * - * A meta base is an opaque container for a base class to be used to walk - * through hierarchies. + * @brief `std::tuple_size` specialization for `compressed_pair`s. + * @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 +struct tuple_size>: integral_constant {}; + +/** + * @brief `std::tuple_element` specialization for `compressed_pair`s. + * @tparam Index The index of the type to return. + * @tparam First The type of the first element that the pair stores. + * @tparam Second The type of the second element that the pair stores. */ -class meta_base { - /*! @brief A meta factory is allowed to create meta objects. */ - template friend class meta_factory; +template +struct tuple_element>: conditional { + static_assert(Index < 2u, "Index out of bounds"); +}; - inline meta_base(const internal::meta_base_node *curr) ENTT_NOEXCEPT - : node{curr} - {} +} // namespace std +#endif -public: - /*! @brief Default constructor. */ - inline meta_base() ENTT_NOEXCEPT - : node{nullptr} - {} +#endif - /** - * @brief Returns the meta type to which a meta base belongs. - * @return The meta type to which the meta base belongs. - */ - inline meta_type parent() const ENTT_NOEXCEPT; +// #include "../core/iterator.hpp" +#ifndef ENTT_CORE_ITERATOR_HPP +#define ENTT_CORE_ITERATOR_HPP + +#include +#include +#include +// #include "../config/config.h" + + +namespace entt { + +/** + * @brief Helper type to use as pointer with input iterators. + * @tparam Type of wrapped value. + */ +template +struct input_iterator_pointer final { + /*! @brief Pointer type. */ + using pointer = Type *; + + /*! @brief Default copy constructor, deleted on purpose. */ + input_iterator_pointer(const input_iterator_pointer &) = delete; + + /*! @brief Default move constructor. */ + input_iterator_pointer(input_iterator_pointer &&) = default; /** - * @brief Returns the meta type of a given meta base. - * @return The meta type of the meta base. + * @brief Constructs a proxy object by move. + * @param val Value to use to initialize the proxy object. */ - inline meta_type type() const ENTT_NOEXCEPT; + input_iterator_pointer(Type &&val) + : value{std::move(val)} {} /** - * @brief Casts an instance from a parent type to a base type. - * @param instance The instance to cast. - * @return An opaque pointer to the base type. + * @brief Default copy assignment operator, deleted on purpose. + * @return This proxy object. */ - inline void * cast(void *instance) const ENTT_NOEXCEPT { - return node->cast(instance); - } + input_iterator_pointer &operator=(const input_iterator_pointer &) = delete; /** - * @brief Returns true if a meta object is valid, false otherwise. - * @return True if the meta object is valid, false otherwise. + * @brief Default move assignment operator. + * @return This proxy object. */ - inline explicit operator bool() const ENTT_NOEXCEPT { - return node; - } + input_iterator_pointer &operator=(input_iterator_pointer &&) = default; /** - * @brief Checks if two meta objects refer to the same node. - * @param other The meta object with which to compare. - * @return True if the two meta objects refer to the same node, false - * otherwise. + * @brief Access operator for accessing wrapped values. + * @return A pointer to the wrapped value. */ - inline bool operator==(const meta_base &other) const ENTT_NOEXCEPT { - return node == other.node; + [[nodiscard]] pointer operator->() ENTT_NOEXCEPT { + return std::addressof(value); } private: - const internal::meta_base_node *node; + Type value; }; - /** - * @brief Checks if two meta objects refer to the same node. - * @param lhs A meta object, either valid or not. - * @param rhs A meta object, either valid or not. - * @return True if the two meta objects refer to the same node, false otherwise. + * @brief Utility class to create an iterable object from a pair of iterators. + * @tparam It Type of iterator. + * @tparam Sentinel Type of sentinel. */ -inline bool operator!=(const meta_base &lhs, const meta_base &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); -} - - -/** - * @brief Meta conversion function object. - * - * A meta conversion function is an opaque container for a conversion function - * to be used to convert a given instance to another type. - */ -class meta_conv { - /*! @brief A meta factory is allowed to create meta objects. */ - template friend class meta_factory; - - inline meta_conv(const internal::meta_conv_node *curr) ENTT_NOEXCEPT - : node{curr} - {} +template +struct iterable_adaptor final { + /*! @brief Value type. */ + using value_type = typename std::iterator_traits::value_type; + /*! @brief Iterator type. */ + using iterator = It; + /*! @brief Sentinel type. */ + using sentinel = Sentinel; -public: /*! @brief Default constructor. */ - inline meta_conv() ENTT_NOEXCEPT - : node{nullptr} - {} + iterable_adaptor() = default; /** - * @brief Returns the meta type to which a meta conversion function belongs. - * @return The meta type to which the meta conversion function belongs. + * @brief Creates an iterable object from a pair of iterators. + * @param from Begin iterator. + * @param to End iterator. */ - inline meta_type parent() const ENTT_NOEXCEPT; + iterable_adaptor(iterator from, sentinel to) + : first{from}, + last{to} {} /** - * @brief Returns the meta type of a given meta conversion function. - * @return The meta type of the meta conversion function. + * @brief Returns an iterator to the beginning. + * @return An iterator to the first element of the range. */ - inline meta_type type() const ENTT_NOEXCEPT; + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return first; + } /** - * @brief Converts an instance to a given type. - * @param instance The instance to convert. - * @return An opaque pointer to the instance to convert. + * @brief Returns an iterator to the end. + * @return An iterator to the element following the last element of the + * range. */ - inline meta_any convert(void *instance) const ENTT_NOEXCEPT { - return node->conv(instance); + [[nodiscard]] sentinel end() const ENTT_NOEXCEPT { + return last; } - /** - * @brief Returns true if a meta object is valid, false otherwise. - * @return True if the meta object is valid, false otherwise. - */ - inline explicit operator bool() const ENTT_NOEXCEPT { - return node; + /*! @copydoc begin */ + [[nodiscard]] iterator cbegin() const ENTT_NOEXCEPT { + return begin(); } - /** - * @brief Checks if two meta objects refer to the same node. - * @param other The meta object with which to compare. - * @return True if the two meta objects refer to the same node, false - * otherwise. - */ - inline bool operator==(const meta_conv &other) const ENTT_NOEXCEPT { - return node == other.node; + /*! @copydoc end */ + [[nodiscard]] sentinel cend() const ENTT_NOEXCEPT { + return end(); } private: - const internal::meta_conv_node *node; + It first; + Sentinel last; }; +} // namespace entt + +#endif + +// #include "../core/memory.hpp" +#ifndef ENTT_CORE_MEMORY_HPP +#define ENTT_CORE_MEMORY_HPP + +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" + + +namespace entt { /** - * @brief Checks if two meta objects refer to the same node. - * @param lhs A meta object, either valid or not. - * @param rhs A meta object, either valid or not. - * @return True if the two meta objects refer to the same node, false otherwise. + * @brief Unwraps fancy pointers, does nothing otherwise (waiting for C++20). + * @tparam Type Pointer type. + * @param ptr Fancy or raw pointer. + * @return A raw pointer that represents the address of the original pointer. */ -inline bool operator!=(const meta_conv &lhs, const meta_conv &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); +template +[[nodiscard]] constexpr auto to_address(Type &&ptr) ENTT_NOEXCEPT { + if constexpr(std::is_pointer_v>>) { + return ptr; + } else { + return to_address(std::forward(ptr).operator->()); + } } - /** - * @brief Meta constructor object. - * - * A meta constructor is an opaque container for a function to be used to - * construct instances of a given type. + * @brief Utility function to design allocation-aware containers. + * @tparam Allocator Type of allocator. + * @param lhs A valid allocator. + * @param rhs Another valid allocator. */ -class meta_ctor { - /*! @brief A meta factory is allowed to create meta objects. */ - template friend class meta_factory; - - inline meta_ctor(const internal::meta_ctor_node *curr) ENTT_NOEXCEPT - : node{curr} - {} - -public: - /*! @brief Unsigned integer type. */ - using size_type = typename internal::meta_ctor_node::size_type; +template +constexpr void propagate_on_container_copy_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) ENTT_NOEXCEPT { + if constexpr(std::allocator_traits::propagate_on_container_copy_assignment::value) { + lhs = rhs; + } +} - /*! @brief Default constructor. */ - inline meta_ctor() ENTT_NOEXCEPT - : node{nullptr} - {} +/** + * @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) ENTT_NOEXCEPT { + if constexpr(std::allocator_traits::propagate_on_container_move_assignment::value) { + lhs = std::move(rhs); + } +} - /** - * @brief Returns the meta type to which a meta constructor belongs. - * @return The meta type to which the meta constructor belongs. - */ - inline meta_type parent() const ENTT_NOEXCEPT; +/** + * @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) ENTT_NOEXCEPT { + ENTT_ASSERT(std::allocator_traits::propagate_on_container_swap::value || lhs == rhs, "Cannot swap the containers"); - /** - * @brief Returns the number of arguments accepted by a meta constructor. - * @return The number of arguments accepted by the meta constructor. - */ - inline size_type size() const ENTT_NOEXCEPT { - return node->size; + if constexpr(std::allocator_traits::propagate_on_container_swap::value) { + using std::swap; + swap(lhs, rhs); } +} - /** - * @brief Returns the meta type of the i-th argument of a meta constructor. - * @param index The index of the argument of which to return the meta type. - * @return The meta type of the i-th argument of a meta constructor, if any. - */ - inline meta_type arg(size_type index) const ENTT_NOEXCEPT; - - /** - * @brief Creates an instance of the underlying type, if possible. - * - * To create a valid instance, the types of the parameters must coincide - * exactly with those required by the underlying meta constructor. - * Otherwise, an empty and then invalid container is returned. - * - * @tparam Args Types of arguments to use to construct the instance. - * @param args Parameters to use to construct the instance. - * @return A meta any containing the new instance, if any. - */ - template - meta_any invoke(Args &&... args) const { - std::array arguments{{std::forward(args)...}}; - meta_any any{}; +/** + * @brief Checks whether a value is a power of two or not. + * @param value A value that may or may not be a power of two. + * @return True if the value is a power of two, false otherwise. + */ +[[nodiscard]] inline constexpr bool is_power_of_two(const std::size_t value) ENTT_NOEXCEPT { + return value && ((value & (value - 1)) == 0); +} - if(sizeof...(Args) == size()) { - any = node->invoke(arguments.data()); - } +/** + * @brief Computes the smallest power of two greater than or equal to a value. + * @param value The value to use. + * @return The smallest power of two greater than or equal to the given value. + */ +[[nodiscard]] inline constexpr std::size_t next_power_of_two(const std::size_t value) ENTT_NOEXCEPT { + ENTT_ASSERT(value < (std::size_t{1u} << (std::numeric_limits::digits - 1)), "Numeric limits exceeded"); + std::size_t curr = value - (value != 0u); - return any; + for(int next = 1; next < std::numeric_limits::digits; next = next * 2) { + curr |= curr >> next; } - /** - * @brief Iterates all the properties assigned to a meta constructor. - * @tparam Op Type of the function object to invoke. - * @param op A valid function object. - */ - template - inline std::enable_if_t, void> - prop(Op op) const ENTT_NOEXCEPT { - internal::iterate([op = std::move(op)](auto *curr) { - op(curr->meta()); - }, node->prop); - } + return ++curr; +} - /** - * @brief Returns the property associated with a given key. - * @tparam Key Type of key to use to search for a property. - * @param key The key to use to search for a property. - * @return The property associated with the given key, if any. - */ - template - inline std::enable_if_t, meta_prop> - prop(Key &&key) const ENTT_NOEXCEPT { - const auto *curr = internal::find_if([key = meta_any{std::forward(key)}](auto *candidate) { - return candidate->key() == key; - }, node->prop); +/** + * @brief Fast module utility function (powers of two only). + * @param value A value for which to calculate the modulus. + * @param mod _Modulus_, it must be a power of two. + * @return The common remainder. + */ +[[nodiscard]] inline constexpr std::size_t fast_mod(const std::size_t value, const std::size_t mod) ENTT_NOEXCEPT { + ENTT_ASSERT(is_power_of_two(mod), "Value must be a power of two"); + return value & (mod - 1u); +} - return curr ? curr->meta() : meta_prop{}; - } +/** + * @brief Deleter for allocator-aware unique pointers (waiting for C++20). + * @tparam Args Types of arguments to use to construct the object. + */ +template +struct allocation_deleter: private Allocator { + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Pointer type. */ + using pointer = typename std::allocator_traits::pointer; /** - * @brief Returns true if a meta object is valid, false otherwise. - * @return True if the meta object is valid, false otherwise. + * @brief Inherited constructors. + * @param alloc The allocator to use. */ - inline explicit operator bool() const ENTT_NOEXCEPT { - return node; - } + allocation_deleter(const allocator_type &alloc) + : Allocator{alloc} {} /** - * @brief Checks if two meta objects refer to the same node. - * @param other The meta object with which to compare. - * @return True if the two meta objects refer to the same node, false - * otherwise. + * @brief Destroys the pointed object and deallocates its memory. + * @param ptr A valid pointer to an object of the given type. */ - inline bool operator==(const meta_ctor &other) const ENTT_NOEXCEPT { - return node == other.node; + void operator()(pointer ptr) { + using alloc_traits = typename std::allocator_traits; + alloc_traits::destroy(*this, to_address(ptr)); + alloc_traits::deallocate(*this, ptr, 1u); } - -private: - const internal::meta_ctor_node *node; }; - /** - * @brief Checks if two meta objects refer to the same node. - * @param lhs A meta object, either valid or not. - * @param rhs A meta object, either valid or not. - * @return True if the two meta objects refer to the same node, false otherwise. + * @brief Allows `std::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. */ -inline bool operator!=(const meta_ctor &lhs, const meta_ctor &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); -} +template +auto allocate_unique(Allocator &allocator, Args &&...args) { + static_assert(!std::is_array_v, "Array types are not supported"); + + using alloc_traits = typename std::allocator_traits::template rebind_traits; + using allocator_type = typename alloc_traits::allocator_type; + + allocator_type alloc{allocator}; + auto ptr = alloc_traits::allocate(alloc, 1u); + + ENTT_TRY { + alloc_traits::construct(alloc, to_address(ptr), std::forward(args)...); + } + ENTT_CATCH { + alloc_traits::deallocate(alloc, ptr, 1u); + ENTT_THROW; + } + return std::unique_ptr>{ptr, alloc}; +} /** - * @brief Meta destructor object. - * - * A meta destructor is an opaque container for a function to be used to - * destroy instances of a given type. + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. */ -class meta_dtor { - /*! @brief A meta factory is allowed to create meta objects. */ - template friend class meta_factory; - inline meta_dtor(const internal::meta_dtor_node *curr) ENTT_NOEXCEPT - : node{curr} - {} +namespace internal { -public: - /*! @brief Default constructor. */ - inline meta_dtor() ENTT_NOEXCEPT - : node{nullptr} - {} +template +struct uses_allocator_construction { + template + static constexpr auto args([[maybe_unused]] const Allocator &allocator, Params &&...params) ENTT_NOEXCEPT { + if constexpr(!std::uses_allocator_v && std::is_constructible_v) { + return std::forward_as_tuple(std::forward(params)...); + } else { + static_assert(std::uses_allocator_v, "Ill-formed request"); - /** - * @brief Returns the meta type to which a meta destructor belongs. - * @return The meta type to which the meta destructor belongs. - */ - inline meta_type parent() const ENTT_NOEXCEPT; + if constexpr(std::is_constructible_v) { + return std::tuple(std::allocator_arg, allocator, std::forward(params)...); + } else { + static_assert(std::is_constructible_v, "Ill-formed request"); + return std::forward_as_tuple(std::forward(params)..., allocator); + } + } + } +}; - /** - * @brief Destroys an instance of the underlying type. - * - * It must be possible to cast the instance to the parent type of the meta - * destructor. Otherwise, invoking the meta destructor results in an - * undefined behavior. - * - * @param handle An opaque pointer to an instance of the underlying type. - * @return True in case of success, false otherwise. - */ - inline bool invoke(meta_handle handle) const { - return node->invoke(handle); +template +struct uses_allocator_construction> { + using type = std::pair; + + template + static constexpr auto args(const Allocator &allocator, std::piecewise_construct_t, First &&first, Second &&second) ENTT_NOEXCEPT { + return std::make_tuple( + std::piecewise_construct, + std::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, std::forward(curr)...); }, std::forward(first)), + std::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, std::forward(curr)...); }, std::forward(second))); } - /** - * @brief Returns true if a meta object is valid, false otherwise. - * @return True if the meta object is valid, false otherwise. - */ - inline explicit operator bool() const ENTT_NOEXCEPT { - return node; + template + static constexpr auto args(const Allocator &allocator) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::tuple<>{}, std::tuple<>{}); } - /** - * @brief Checks if two meta objects refer to the same node. - * @param other The meta object with which to compare. - * @return True if the two meta objects refer to the same node, false - * otherwise. - */ - inline bool operator==(const meta_dtor &other) const ENTT_NOEXCEPT { - return node == other.node; + template + static constexpr auto args(const Allocator &allocator, First &&first, Second &&second) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::forward(first)), std::forward_as_tuple(std::forward(second))); } -private: - const internal::meta_dtor_node *node; + template + static constexpr auto args(const Allocator &allocator, const std::pair &value) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(value.first), std::forward_as_tuple(value.second)); + } + + template + static constexpr auto args(const Allocator &allocator, std::pair &&value) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::move(value.first)), std::forward_as_tuple(std::move(value.second))); + } }; +} // namespace internal /** - * @brief Checks if two meta objects refer to the same node. - * @param lhs A meta object, either valid or not. - * @param rhs A meta object, either valid or not. - * @return True if the two meta objects refer to the same node, false otherwise. + * Internal details not to be documented. + * @endcond */ -inline bool operator!=(const meta_dtor &lhs, const meta_dtor &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); + +/** + * @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 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 The arguments needed to create an object of the given type. + */ +template +constexpr auto uses_allocator_construction_args(const Allocator &allocator, Args &&...args) ENTT_NOEXCEPT { + return internal::uses_allocator_construction::args(allocator, std::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 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 newly created object of the given type. + */ +template +constexpr Type make_obj_using_allocator(const Allocator &allocator, Args &&...args) { + return std::make_from_tuple(internal::uses_allocator_construction::args(allocator, std::forward(args)...)); +} /** - * @brief Meta data object. + * @brief Uses-allocator construction utility (waiting for C++20). * - * A meta data is an opaque container for a data member associated with a given - * type. + * 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 Allocator Type of allocator used to manage memory and elements. + * @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. */ -class meta_data { - /*! @brief A meta factory is allowed to create meta objects. */ - template friend class meta_factory; +template +constexpr Type *uninitialized_construct_using_allocator(Type *value, const Allocator &allocator, Args &&...args) { + return std::apply([&](auto &&...curr) { return new(value) Type(std::forward(curr)...); }, internal::uses_allocator_construction::args(allocator, std::forward(args)...)); +} - inline meta_data(const internal::meta_data_node *curr) ENTT_NOEXCEPT - : node{curr} - {} +} // namespace entt -public: - /*! @brief Default constructor. */ - inline meta_data() ENTT_NOEXCEPT - : node{nullptr} - {} +#endif - /** - * @brief Returns the name assigned to a given meta data. - * @return The name assigned to the meta data. - */ - inline const char * name() const ENTT_NOEXCEPT { - return node->name; - } +// #include "../core/type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP - /** - * @brief Returns the meta type to which a meta data belongs. - * @return The meta type to which the meta data belongs. - */ - inline meta_type parent() const ENTT_NOEXCEPT; +#include +#include +#include +#include +// #include "../config/config.h" - /** - * @brief Indicates whether a given meta data is constant or not. - * @return True if the meta data is constant, false otherwise. - */ - inline bool is_const() const ENTT_NOEXCEPT { - return node->is_const; - } +// #include "fwd.hpp" - /** - * @brief Indicates whether a given meta data is static or not. - * - * A static meta data is such that it can be accessed using a null pointer - * as an instance. - * - * @return True if the meta data is static, false otherwise. - */ - inline bool is_static() const ENTT_NOEXCEPT { - return node->is_static; - } - /** - * @brief Returns the meta type of a given meta data. - * @return The meta type of the meta data. - */ - inline meta_type type() const ENTT_NOEXCEPT; +namespace entt { - /** - * @brief Sets the value of the variable enclosed by a given meta type. - * - * It must be possible to cast the instance to the parent type of the meta - * data. Otherwise, invoking the setter results in an undefined - * behavior.
- * The type of the value must coincide exactly with that of the variable - * enclosed by the meta data. Otherwise, invoking the setter does nothing. - * - * @tparam Type Type of value to assign. - * @param handle An opaque pointer to an instance of the underlying type. - * @param value Parameter to use to set the underlying variable. - * @return True in case of success, false otherwise. - */ - template - inline bool set(meta_handle handle, Type &&value) const { - return node->set(handle, meta_any{}, std::forward(value)); - } +/** + * @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 TURN_OFF_DOXYGEN */ choice_t /*! @endcond */ +{}; - /** - * @brief Sets the i-th element of an array enclosed by a given meta type. - * - * It must be possible to cast the instance to the parent type of the meta - * data. Otherwise, invoking the setter results in an undefined - * behavior.
- * The type of the value must coincide exactly with that of the array type - * enclosed by the meta data. Otherwise, invoking the setter does nothing. - * - * @tparam Type Type of value to assign. - * @param handle An opaque pointer to an instance of the underlying type. - * @param index Position of the underlying element to set. - * @param value Parameter to use to set the underlying element. - * @return True in case of success, false otherwise. - */ - template - inline bool set(meta_handle handle, std::size_t index, Type &&value) const { - ENTT_ASSERT(index < node->type()->extent); - return node->set(handle, index, std::forward(value)); - } +/*! @copybrief choice_t */ +template<> +struct choice_t<0> {}; - /** - * @brief Gets the value of the variable enclosed by a given meta type. - * - * It must be possible to cast the instance to the parent type of the meta - * data. Otherwise, invoking the getter results in an undefined behavior. - * - * @param handle An opaque pointer to an instance of the underlying type. - * @return A meta any containing the value of the underlying variable. - */ - inline meta_any get(meta_handle handle) const ENTT_NOEXCEPT { - return node->get(handle, meta_any{}); - } +/** + * @brief Variable template for the choice trick. + * @tparam N Number of choices available. + */ +template +inline constexpr choice_t choice{}; - /** - * @brief Gets the i-th element of an array enclosed by a given meta type. - * - * It must be possible to cast the instance to the parent type of the meta - * data. Otherwise, invoking the getter results in an undefined behavior. - * - * @param handle An opaque pointer to an instance of the underlying type. - * @param index Position of the underlying element to get. - * @return A meta any containing the value of the underlying element. - */ - inline meta_any get(meta_handle handle, std::size_t index) const ENTT_NOEXCEPT { - ENTT_ASSERT(index < node->type()->extent); - return node->get(handle, index); - } +/** + * @brief Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; - /** - * @brief Iterates all the properties assigned to a meta data. - * @tparam Op Type of the function object to invoke. - * @param op A valid function object. - */ - template - inline std::enable_if_t, void> - prop(Op op) const ENTT_NOEXCEPT { - internal::iterate([op = std::move(op)](auto *curr) { - op(curr->meta()); - }, node->prop); - } +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; - /** - * @brief Returns the property associated with a given key. - * @tparam Key Type of key to use to search for a property. - * @param key The key to use to search for a property. - * @return The property associated with the given key, if any. - */ - template - inline std::enable_if_t, meta_prop> - prop(Key &&key) const ENTT_NOEXCEPT { - const auto *curr = internal::find_if([key = meta_any{std::forward(key)}](auto *candidate) { - return candidate->key() == key; - }, node->prop); +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; - return curr ? curr->meta() : meta_prop{}; - } +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; - /** - * @brief Returns true if a meta object is valid, false otherwise. - * @return True if the meta object is valid, false otherwise. - */ - inline explicit operator bool() const ENTT_NOEXCEPT { - return node; - } +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::size_t size_of_v = size_of::value; - /** - * @brief Checks if two meta objects refer to the same node. - * @param other The meta object with which to compare. - * @return True if the two meta objects refer to the same node, false - * otherwise. - */ - inline bool operator==(const meta_data &other) const ENTT_NOEXCEPT { - return node == other.node; - } +/** + * @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; -private: - const internal::meta_data_node *node; +/** + * @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 = std::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 Checks if two meta objects refer to the same node. - * @param lhs A meta object, either valid or not. - * @param rhs A meta object, either valid or not. - * @return True if the two meta objects refer to the same node, false otherwise. + * @brief Provides compile-time indexed access to the types of a type list. + * @tparam Index Index of the type to return. + * @tparam Type First type provided by the type list. + * @tparam Other Other types provided by the type list. */ -inline bool operator!=(const meta_data &lhs, const meta_data &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); -} +template +struct type_list_element> + : type_list_element> {}; +/** + * @brief Provides compile-time indexed access to the types of a type list. + * @tparam Type 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 = Type; +}; /** - * @brief Meta function object. - * - * A meta function is an opaque container for a member function associated with - * a given type. + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. */ -class meta_func { - /*! @brief A meta factory is allowed to create meta objects. */ - template friend class meta_factory; +template +using type_list_element_t = typename type_list_element::type; - inline meta_func(const internal::meta_func_node *curr) ENTT_NOEXCEPT - : node{curr} - {} +/** + * @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 +constexpr type_list operator+(type_list, type_list) { + return {}; +} -public: - /*! @brief Unsigned integer type. */ - using size_type = typename internal::meta_func_node::size_type; +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_cat; - /*! @brief Default constructor. */ - inline meta_func() ENTT_NOEXCEPT - : node{nullptr} - {} +/*! @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 Returns the name assigned to a given meta function. - * @return The name assigned to the meta function. - */ - inline const char * name() const ENTT_NOEXCEPT { - return node->name; - } +/** + * @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 = typename type_list_cat, List...>::type; +}; - /** - * @brief Returns the meta type to which a meta function belongs. - * @return The meta type to which the meta function belongs. - */ - inline meta_type parent() const ENTT_NOEXCEPT; +/** + * @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 Returns the number of arguments accepted by a meta function. - * @return The number of arguments accepted by the meta function. - */ - inline size_type size() const ENTT_NOEXCEPT { - return node->size; - } +/** + * @brief Helper type. + * @tparam List Type lists to concatenate. + */ +template +using type_list_cat_t = typename type_list_cat::type; - /** - * @brief Indicates whether a given meta function is constant or not. - * @return True if the meta function is constant, false otherwise. - */ - inline bool is_const() const ENTT_NOEXCEPT { - return node->is_const; - } +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; - /** - * @brief Indicates whether a given meta function is static or not. - * - * A static meta function is such that it can be invoked using a null - * pointer as an instance. - * - * @return True if the meta function is static, false otherwise. - */ - inline bool is_static() const ENTT_NOEXCEPT { - return node->is_static; - } +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; +}; - /** - * @brief Returns the meta type of the return type of a meta function. - * @return The meta type of the return type of the meta function. - */ - inline meta_type ret() const ENTT_NOEXCEPT; +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; - /** - * @brief Returns the meta type of the i-th argument of a meta function. - * @param index The index of the argument of which to return the meta type. - * @return The meta type of the i-th argument of a meta function, if any. - */ - inline meta_type arg(size_type index) const ENTT_NOEXCEPT; +/** + * @brief Helper type. + * @tparam Type A type list. + */ +template +using type_list_unique_t = typename type_list_unique::type; - /** - * @brief Invokes the underlying function, if possible. - * - * To invoke a meta function, the types of the parameters must coincide - * exactly with those required by the underlying function. Otherwise, an - * empty and then invalid container is returned.
- * It must be possible to cast the instance to the parent type of the meta - * function. Otherwise, invoking the underlying function results in an - * undefined behavior. - * - * @tparam Args Types of arguments to use to invoke the function. - * @param handle An opaque pointer to an instance of the underlying type. - * @param args Parameters to use to invoke the function. - * @return A meta any containing the returned value, if any. - */ - template - meta_any invoke(meta_handle handle, Args &&... args) const { - std::array arguments{{std::forward(args)...}}; - meta_any any{}; +/** + * @brief Provides the member constant `value` 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; - if(sizeof...(Args) == size()) { - any = node->invoke(handle, arguments.data()); - } +/** + * @copybrief type_list_contains + * @tparam Type Types provided by the type list. + * @tparam Other Type to look for. + */ +template +struct type_list_contains, Other>: std::disjunction...> {}; - return any; - } +/** + * @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 Iterates all the properties assigned to a meta function. - * @tparam Op Type of the function object to invoke. - * @param op A valid function object. - */ - template - inline std::enable_if_t, void> - prop(Op op) const ENTT_NOEXCEPT { - internal::iterate([op = std::move(op)](auto *curr) { - op(curr->meta()); - }, node->prop); - } +/** + * @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 Returns the property associated with a given key. - * @tparam Key Type of key to use to search for a property. - * @param key The key to use to search for a property. - * @return The property associated with the given key, if any. - */ - template - inline std::enable_if_t, meta_prop> - prop(Key &&key) const ENTT_NOEXCEPT { - const auto *curr = internal::find_if([key = meta_any{std::forward(key)}](auto *candidate) { - return candidate->key() == key; - }, node->prop); +/** + * @brief Helper type. + * @tparam List Type lists between which to compute the difference. + */ +template +using type_list_diff_t = typename type_list_diff::type; - return curr ? curr->meta() : meta_prop{}; - } +/** + * @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 Returns true if a meta object is valid, false otherwise. - * @return True if the meta object is valid, false otherwise. - */ - inline explicit operator bool() const ENTT_NOEXCEPT { - return node; - } +/*! @brief Primary template isn't defined on purpose. */ +template +struct value_list_element; - /** - * @brief Checks if two meta objects refer to the same node. - * @param other The meta object with which to compare. - * @return True if the two meta objects refer to the same node, false - * otherwise. - */ - inline bool operator==(const meta_func &other) const ENTT_NOEXCEPT { - return node == other.node; - } +/** + * @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> {}; -private: - const internal::meta_func_node *node; +/** + * @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 value. */ + static constexpr auto value = Value; }; +/** + * @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 Checks if two meta objects refer to the same node. - * @param lhs A meta object, either valid or not. - * @param rhs A meta object, either valid or not. - * @return True if the two meta objects refer to the same node, false otherwise. + * @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. */ -inline bool operator!=(const meta_func &lhs, const meta_func &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); +template +constexpr 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 Meta type object. - * - * A meta type is the starting point for accessing a reflected type, thus being - * able to work through it on real objects. + * @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. */ -class meta_type { - /*! @brief A meta factory is allowed to create meta objects. */ - template friend class meta_factory; - - /*! @brief A meta node is allowed to create meta objects. */ - template friend struct internal::meta_node; - - inline meta_type(const internal::meta_type_node *curr) ENTT_NOEXCEPT - : node{curr} - {} +template +struct value_list_cat, value_list, List...> { + /*! @brief A value list composed by the values of all the value lists. */ + using type = typename value_list_cat, List...>::type; +}; -public: - /*! @brief Unsigned integer type. */ - using size_type = typename internal::meta_type_node::size_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 Default constructor. */ - inline meta_type() ENTT_NOEXCEPT - : node{nullptr} - {} +/** + * @brief Helper type. + * @tparam List Value lists to concatenate. + */ +template +using value_list_cat_t = typename value_list_cat::type; - /** - * @brief Returns the name assigned to a given meta type. - * @return The name assigned to the meta type. - */ - inline const char * name() const ENTT_NOEXCEPT { - return node->name; - } +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::false_type {}; - /** - * @brief Indicates whether a given meta type refers to void or not. - * @return True if the underlying type is void, false otherwise. - */ - inline bool is_void() const ENTT_NOEXCEPT { - return node->is_void; - } +/** + * @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>: std::is_invocable {}; - /** - * @brief Indicates whether a given meta type refers to an integral type or - * not. - * @return True if the underlying type is an integral type, false otherwise. - */ - inline bool is_integral() const ENTT_NOEXCEPT { - return node->is_integral; - } +/** + * @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>: std::is_invocable {}; - /** - * @brief Indicates whether a given meta type refers to a floating-point - * type or not. - * @return True if the underlying type is a floating-point type, false - * otherwise. - */ - inline bool is_floating_point() const ENTT_NOEXCEPT { - return node->is_floating_point; - } +/** + * @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 Indicates whether a given meta type refers to an array type or - * not. - * @return True if the underlying type is an array type, false otherwise. - */ - inline bool is_array() const ENTT_NOEXCEPT { - return node->is_array; - } +/*! @brief Same as std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::false_type {}; - /** - * @brief Indicates whether a given meta type refers to an enum or not. - * @return True if the underlying type is an enum, false otherwise. - */ - inline bool is_enum() const ENTT_NOEXCEPT { - return node->is_enum; - } +/** + * @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>: std::is_invocable_r {}; - /** - * @brief Indicates whether a given meta type refers to an union or not. - * @return True if the underlying type is an union, false otherwise. - */ - inline bool is_union() const ENTT_NOEXCEPT { - return node->is_union; - } +/** + * @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 Indicates whether a given meta type refers to a class or not. - * @return True if the underlying type is a class, false otherwise. - */ - inline bool is_class() const ENTT_NOEXCEPT { - return node->is_class; - } +/** + * @brief Provides the member constant `value` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; - /** - * @brief Indicates whether a given meta type refers to a pointer or not. - * @return True if the underlying type is a pointer, false otherwise. - */ - inline bool is_pointer() const ENTT_NOEXCEPT { - return node->is_pointer; - } +/*! @copydoc is_complete */ +template +struct is_complete>: std::true_type {}; - /** - * @brief Indicates whether a given meta type refers to a function type or - * not. - * @return True if the underlying type is a function, false otherwise. - */ - inline bool is_function() const ENTT_NOEXCEPT { - return node->is_function; - } +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_complete_v = is_complete::value; - /** - * @brief Indicates whether a given meta type refers to a pointer to data - * member or not. - * @return True if the underlying type is a pointer to data member, false - * otherwise. - */ - inline bool is_member_object_pointer() const ENTT_NOEXCEPT { - return node->is_member_object_pointer; - } +/** + * @brief Provides the member constant `value` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; - /** - * @brief Indicates whether a given meta type refers to a pointer to member - * function or not. - * @return True if the underlying type is a pointer to member function, - * false otherwise. - */ - inline bool is_member_function_pointer() const ENTT_NOEXCEPT { - return node->is_member_function_pointer; - } +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ - /** - * @brief If a given meta type refers to an array type, provides the number - * of elements of the array. - * @return The number of elements of the array if the underlying type is an - * array type, 0 otherwise. - */ - inline size_type extent() const ENTT_NOEXCEPT { - return node->extent; - } +namespace internal { - /** - * @brief Provides the meta type for which the pointer is defined. - * @return The meta type for which the pointer is defined or this meta type - * if it doesn't refer to a pointer type. - */ - inline meta_type remove_pointer() const ENTT_NOEXCEPT { - return node->remove_pointer(); - } +template +struct has_iterator_category: std::false_type {}; - /** - * @brief Iterates all the meta base of a meta type. - * - * Iteratively returns **all** the base classes of the given type. - * - * @tparam Op Type of the function object to invoke. - * @param op A valid function object. - */ - template - inline void base(Op op) const ENTT_NOEXCEPT { - internal::iterate<&internal::meta_type_node::base>([op = std::move(op)](auto *curr) { - op(curr->meta()); - }, node); - } +template +struct has_iterator_category::iterator_category>>: std::true_type {}; - /** - * @brief Returns the meta base associated with a given name. - * - * Searches recursively among **all** the base classes of the given type. - * - * @param str The name to use to search for a meta base. - * @return The meta base associated with the given name, if any. - */ - inline meta_base base(const char *str) const ENTT_NOEXCEPT { - const auto *curr = internal::find_if<&internal::meta_type_node::base>([name = hashed_string{str}](auto *candidate) { - return candidate->type()->name == name; - }, node); +} // namespace internal - return curr ? curr->meta() : meta_base{}; - } +/** + * Internal details not to be documented. + * @endcond + */ - /** - * @brief Iterates all the meta conversion functions of a meta type. - * - * Iteratively returns **all** the meta conversion functions of the given - * type. - * - * @tparam Op Type of the function object to invoke. - * @param op A valid function object. - */ - template - inline void conv(Op op) const ENTT_NOEXCEPT { - internal::iterate<&internal::meta_type_node::conv>([op = std::move(op)](auto *curr) { - op(curr->meta()); - }, node); - } +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : internal::has_iterator_category {}; - /** - * @brief Returns the meta conversion function associated with a given type. - * - * Searches recursively among **all** the conversion functions of the given - * type. - * - * @tparam Type The type to use to search for a meta conversion function. - * @return The meta conversion function associated with the given type, if - * any. - */ - template - inline meta_conv conv() const ENTT_NOEXCEPT { - const auto *curr = internal::find_if<&internal::meta_type_node::conv>([type = internal::meta_info::resolve()](auto *candidate) { - return candidate->type() == type; - }, node); +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_iterator_v = is_iterator::value; - return curr ? curr->meta() : meta_conv{}; - } +/** + * @brief Provides the member constant `value` 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 + : std::conjunction, std::negation>> {}; - /** - * @brief Iterates all the meta constructors of a meta type. - * @tparam Op Type of the function object to invoke. - * @param op A valid function object. - */ - template - inline void ctor(Op op) const ENTT_NOEXCEPT { - internal::iterate([op = std::move(op)](auto *curr) { - op(curr->meta()); - }, node->ctor); - } +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_ebco_eligible_v = is_ebco_eligible::value; - /** - * @brief Returns the meta constructor that accepts a given list of types of - * arguments. - * @return The requested meta constructor, if any. - */ - template - inline meta_ctor ctor() const ENTT_NOEXCEPT { - const auto *curr = internal::ctor(std::make_index_sequence{}, node); - return curr ? curr->meta() : meta_ctor{}; - } +/** + * @brief Provides the member constant `value` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; - /** - * @brief Returns the meta destructor associated with a given type. - * @return The meta destructor associated with the given type, if any. - */ - inline meta_dtor dtor() const ENTT_NOEXCEPT { - return node->dtor ? node->dtor->meta() : meta_dtor{}; - } +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; - /** - * @brief Iterates all the meta data of a meta type. - * - * Iteratively returns **all** the meta data of the given type. This means - * that the meta data of the base classes will also be returned, if any. - * - * @tparam Op Type of the function object to invoke. - * @param op A valid function object. - */ - template - inline void data(Op op) const ENTT_NOEXCEPT { - internal::iterate<&internal::meta_type_node::data>([op = std::move(op)](auto *curr) { - op(curr->meta()); - }, node); - } +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; - /** - * @brief Returns the meta data associated with a given name. - * - * Searches recursively among **all** the meta data of the given type. This - * means that the meta data of the base classes will also be inspected, if - * any. - * - * @param str The name to use to search for a meta data. - * @return The meta data associated with the given name, if any. - */ - inline meta_data data(const char *str) const ENTT_NOEXCEPT { - const auto *curr = internal::find_if<&internal::meta_type_node::data>([name = hashed_string{str}](auto *candidate) { - return candidate->name == name; - }, node); +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; - return curr ? curr->meta() : meta_data{}; - } +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ - /** - * @brief Iterates all the meta functions of a meta type. - * - * Iteratively returns **all** the meta functions of the given type. This - * means that the meta functions of the base classes will also be returned, - * if any. - * - * @tparam Op Type of the function object to invoke. - * @param op A valid function object. - */ - template - inline void func(Op op) const ENTT_NOEXCEPT { - internal::iterate<&internal::meta_type_node::func>([op = std::move(op)](auto *curr) { - op(curr->meta()); - }, node); - } +namespace internal { - /** - * @brief Returns the meta function associated with a given name. - * - * Searches recursively among **all** the meta functions of the given type. - * This means that the meta functions of the base classes will also be - * inspected, if any. - * - * @param str The name to use to search for a meta function. - * @return The meta function associated with the given name, if any. - */ - inline meta_func func(const char *str) const ENTT_NOEXCEPT { - const auto *curr = internal::find_if<&internal::meta_type_node::func>([name = hashed_string{str}](auto *candidate) { - return candidate->name == name; - }, node); +template +struct has_tuple_size_value: std::false_type {}; - return curr ? curr->meta() : meta_func{}; - } +template +struct has_tuple_size_value::value)>>: std::true_type {}; - /** - * @brief Creates an instance of the underlying type, if possible. - * - * To create a valid instance, the types of the parameters must coincide - * exactly with those required by the underlying meta constructor. - * Otherwise, an empty and then invalid container is returned. - * - * @tparam Args Types of arguments to use to construct the instance. - * @param args Parameters to use to construct the instance. - * @return A meta any containing the new instance, if any. - */ - template - meta_any construct(Args &&... args) const { - std::array arguments{{std::forward(args)...}}; - meta_any any{}; +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} - internal::iterate<&internal::meta_type_node::ctor>([data = arguments.data(), &any](auto *curr) -> bool { - any = curr->invoke(data); - return static_cast(any); - }, node); +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} - return any; +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; } +} - /** - * @brief Destroys an instance of the underlying type. - * - * It must be possible to cast the instance to the underlying type. - * Otherwise, invoking the meta destructor results in an undefined behavior. - * - * @param handle An opaque pointer to an instance of the underlying type. - * @return True in case of success, false otherwise. - */ - inline bool destroy(meta_handle handle) const { - return node->dtor ? node->dtor->invoke(handle) : node->destroy(handle); +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); } +} - /** - * @brief Iterates all the properties assigned to a meta type. - * - * Iteratively returns **all** the properties of the given type. This means - * that the properties of the base classes will also be returned, if any. - * - * @tparam Op Type of the function object to invoke. - * @param op A valid function object. - */ - template - inline std::enable_if_t, void> - prop(Op op) const ENTT_NOEXCEPT { - internal::iterate<&internal::meta_type_node::prop>([op = std::move(op)](auto *curr) { - op(curr->meta()); - }, node); - } +} // namespace internal - /** - * @brief Returns the property associated with a given key. - * - * Searches recursively among **all** the properties of the given type. This - * means that the properties of the base classes will also be inspected, if - * any. - * - * @tparam Key Type of key to use to search for a property. - * @param key The key to use to search for a property. - * @return The property associated with the given key, if any. - */ - template - inline std::enable_if_t, meta_prop> - prop(Key &&key) const ENTT_NOEXCEPT { - const auto *curr = internal::find_if<&internal::meta_type_node::prop>([key = meta_any{std::forward(key)}](auto *candidate) { - return candidate->key() == key; - }, node); +/** + * Internal details not to be documented. + * @endcond + */ - return curr ? curr->meta() : meta_prop{}; - } +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; - /** - * @brief Returns true if a meta object is valid, false otherwise. - * @return True if the meta object is valid, false otherwise. - */ - inline explicit operator bool() const ENTT_NOEXCEPT { - return node; - } +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_equality_comparable_v = is_equality_comparable::value; - /** - * @brief Checks if two meta objects refer to the same node. - * @param other The meta object with which to compare. - * @return True if the two meta objects refer to the same node, false - * otherwise. - */ - inline bool operator==(const meta_type &other) const ENTT_NOEXCEPT { - return node == other.node; - } +/** + * @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 = std::remove_const_t; +}; -private: - const internal::meta_type_node *node; +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; }; +/** + * @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 = typename constness_as::type; /** - * @brief Checks if two meta objects refer to the same node. - * @param lhs A meta object, either valid or not. - * @param rhs A meta object, either valid or not. - * @return True if the two meta objects refer to the same node, false otherwise. + * @brief Extracts the class of a non-static member object or function. + * @tparam Member A pointer to a non-static member object or function. */ -inline bool operator!=(const meta_type &lhs, const meta_type &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); -} +template +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); + template + static Class *clazz(Ret (Class::*)(Args...)); -inline meta_type meta_any::type() const ENTT_NOEXCEPT { - return node ? node->meta() : meta_type{}; -} + template + static Class *clazz(Ret (Class::*)(Args...) const); + template + static Class *clazz(Type Class::*); -inline meta_type meta_handle::type() const ENTT_NOEXCEPT { - return node ? node->meta() : meta_type{}; -} +public: + /*! @brief The class of the given non-static member object or function. */ + using type = std::remove_pointer_t()))>; +}; +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; -inline meta_type meta_base::parent() const ENTT_NOEXCEPT { - return node->parent->meta(); -} +} // namespace entt +#endif -inline meta_type meta_base::type() const ENTT_NOEXCEPT { - return node->type()->meta(); -} +// #include "fwd.hpp" +#ifndef ENTT_CONTAINER_FWD_HPP +#define ENTT_CONTAINER_FWD_HPP +#include +#include -inline meta_type meta_conv::parent() const ENTT_NOEXCEPT { - return node->parent->meta(); -} +namespace entt { +template< + typename Key, + typename Type, + typename = std::hash, + typename = std::equal_to, + typename = std::allocator>> +class dense_map; -inline meta_type meta_conv::type() const ENTT_NOEXCEPT { - return node->type()->meta(); -} +template< + typename Type, + typename = std::hash, + typename = std::equal_to, + typename = std::allocator> +class dense_set; +} // namespace entt -inline meta_type meta_ctor::parent() const ENTT_NOEXCEPT { - return node->parent->meta(); -} +#endif -inline meta_type meta_ctor::arg(size_type index) const ENTT_NOEXCEPT { - return index < size() ? node->arg(index)->meta() : meta_type{}; -} +namespace entt { +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ -inline meta_type meta_dtor::parent() const ENTT_NOEXCEPT { - return node->parent->meta(); -} +namespace internal { +template +struct dense_map_node final { + using value_type = std::pair; -inline meta_type meta_data::parent() const ENTT_NOEXCEPT { - return node->parent->meta(); -} + template + dense_map_node(const std::size_t pos, Args &&...args) + : next{pos}, + element{std::forward(args)...} {} + + template + dense_map_node(std::allocator_arg_t, const Allocator &allocator, const std::size_t pos, Args &&...args) + : next{pos}, + element{entt::make_obj_using_allocator(allocator, std::forward(args)...)} {} + + template + dense_map_node(std::allocator_arg_t, const Allocator &allocator, const dense_map_node &other) + : next{other.next}, + element{entt::make_obj_using_allocator(allocator, other.element)} {} + + template + dense_map_node(std::allocator_arg_t, const Allocator &allocator, dense_map_node &&other) + : next{other.next}, + element{entt::make_obj_using_allocator(allocator, std::move(other.element))} {} + + std::size_t next; + value_type element; +}; +template +class dense_map_iterator final { + template + friend class dense_map_iterator; -inline meta_type meta_data::type() const ENTT_NOEXCEPT { - return node->type()->meta(); -} + using first_type = decltype(std::as_const(std::declval()->element.first)); + using second_type = decltype((std::declval()->element.second)); +public: + using value_type = std::pair; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; -inline meta_type meta_func::parent() const ENTT_NOEXCEPT { - return node->parent->meta(); -} + dense_map_iterator() ENTT_NOEXCEPT + : it{} {} + dense_map_iterator(const It iter) ENTT_NOEXCEPT + : it{iter} {} -inline meta_type meta_func::ret() const ENTT_NOEXCEPT { - return node->ret()->meta(); -} + template && std::is_constructible_v>> + dense_map_iterator(const dense_map_iterator &other) ENTT_NOEXCEPT + : it{other.it} {} + dense_map_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } -inline meta_type meta_func::arg(size_type index) const ENTT_NOEXCEPT { - return index < size() ? node->arg(index)->meta() : meta_type{}; -} + dense_map_iterator operator++(int) ENTT_NOEXCEPT { + dense_map_iterator orig = *this; + return ++(*this), orig; + } + dense_map_iterator &operator--() ENTT_NOEXCEPT { + return --it, *this; + } -/** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. - */ + dense_map_iterator operator--(int) ENTT_NOEXCEPT { + dense_map_iterator orig = *this; + return operator--(), orig; + } + dense_map_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + it += value; + return *this; + } -namespace internal { + dense_map_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + dense_map_iterator copy = *this; + return (copy += value); + } + dense_map_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); + } -template -struct meta_function_helper; + dense_map_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); + } + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + return {it[value].element.first, it[value].element.second}; + } -template -struct meta_function_helper { - using return_type = Ret; - using args_type = std::tuple; + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } - template - using arg_type = std::decay_t>; + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return {it->element.first, it->element.second}; + } - static constexpr auto size = sizeof...(Args); + template + friend std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) ENTT_NOEXCEPT; - inline static auto arg(typename internal::meta_func_node::size_type index) { - return std::array{{meta_info::resolve()...}}[index]; - } -}; + template + friend bool operator==(const dense_map_iterator &, const dense_map_iterator &) ENTT_NOEXCEPT; + template + friend bool operator<(const dense_map_iterator &, const dense_map_iterator &) ENTT_NOEXCEPT; -template -struct meta_function_helper, std::bool_constant>: meta_function_helper { - using class_type = Class; - static constexpr auto is_const = Const; - static constexpr auto is_static = Static; +private: + It it; }; +template +[[nodiscard]] std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it - rhs.it; +} -template -constexpr meta_function_helper, std::bool_constant> -to_meta_function_helper(Ret(Class:: *)(Args...)); +template +[[nodiscard]] bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} +template +[[nodiscard]] bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} -template -constexpr meta_function_helper, std::bool_constant> -to_meta_function_helper(Ret(Class:: *)(Args...) const); +template +[[nodiscard]] bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it < rhs.it; +} +template +[[nodiscard]] bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} -template -constexpr meta_function_helper, std::bool_constant> -to_meta_function_helper(Ret(*)(Args...)); +template +[[nodiscard]] bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} +template +[[nodiscard]] bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} -template -struct meta_function_helper>: decltype(to_meta_function_helper(Func)) {}; +template +class dense_map_local_iterator final { + template + friend class dense_map_local_iterator; + using first_type = decltype(std::as_const(std::declval()->element.first)); + using second_type = decltype((std::declval()->element.second)); -template -inline bool destroy([[maybe_unused]] meta_handle handle) { - bool accepted = false; +public: + using value_type = std::pair; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; - if constexpr(std::is_object_v && !std::is_array_v) { - accepted = (handle.type() == meta_info::resolve()->meta()); + dense_map_local_iterator() ENTT_NOEXCEPT + : it{}, + offset{} {} - if(accepted) { - static_cast(handle.data())->~Type(); - } + dense_map_local_iterator(It iter, const std::size_t pos) ENTT_NOEXCEPT + : it{iter}, + offset{pos} {} + + template && std::is_constructible_v>> + dense_map_local_iterator(const dense_map_local_iterator &other) ENTT_NOEXCEPT + : it{other.it}, + offset{other.offset} {} + + dense_map_local_iterator &operator++() ENTT_NOEXCEPT { + return offset = it[offset].next, *this; } - return accepted; -} + dense_map_local_iterator operator++(int) ENTT_NOEXCEPT { + dense_map_local_iterator orig = *this; + return ++(*this), orig; + } + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } -template -inline meta_any construct(meta_any * const args, std::index_sequence) { - [[maybe_unused]] std::array can_cast{{(args+Indexes)->can_cast>>()...}}; - [[maybe_unused]] std::array can_convert{{(std::get(can_cast) ? false : (args+Indexes)->can_convert>>())...}}; - meta_any any{}; + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return {it[offset].element.first, it[offset].element.second}; + } - if(((std::get(can_cast) || std::get(can_convert)) && ...)) { - ((std::get(can_convert) ? void((args+Indexes)->convert>>()) : void()), ...); - any = Type{(args+Indexes)->cast>>()...}; + [[nodiscard]] std::size_t index() const ENTT_NOEXCEPT { + return offset; } - return any; +private: + It it; + std::size_t offset; +}; + +template +[[nodiscard]] bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() == rhs.index(); } +template +[[nodiscard]] bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} -template -bool setter([[maybe_unused]] meta_handle handle, [[maybe_unused]] meta_any index, [[maybe_unused]] meta_any value) { - bool accepted = false; +} // namespace internal - if constexpr(!Const) { - if constexpr(std::is_function_v> || std::is_member_function_pointer_v) { - using helper_type = meta_function_helper>; - using data_type = std::decay_t, typename helper_type::args_type>>; - static_assert(std::is_invocable_v); - accepted = value.can_cast() || value.convert(); - auto *clazz = handle.try_cast(); +/** + * Internal details not to be documented. + * @endcond + */ - if(accepted && clazz) { - std::invoke(Data, clazz, value.cast()); - } - } else if constexpr(std::is_member_object_pointer_v) { - using data_type = std::remove_cv_t().*Data)>>; - static_assert(std::is_invocable_v); - auto *clazz = handle.try_cast(); +/** + * @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 std::size_t minimum_capacity = 8u; - if constexpr(std::is_array_v) { - using underlying_type = std::remove_extent_t; - accepted = index.can_cast() && (value.can_cast() || value.convert()); + using node_type = internal::dense_map_node; + using alloc_traits = typename std::allocator_traits; + static_assert(std::is_same_v>, "Invalid value type"); + using sparse_container_type = std::vector>; + using packed_container_type = std::vector>; - if(accepted && clazz) { - std::invoke(Data, clazz)[index.cast()] = value.cast(); - } - } else { - accepted = value.can_cast() || value.convert(); + template + [[nodiscard]] std::size_t key_to_bucket(const Other &key) const ENTT_NOEXCEPT { + return fast_mod(sparse.second()(key), bucket_count()); + } - if(accepted && clazz) { - std::invoke(Data, clazz) = value.cast(); - } + template + [[nodiscard]] auto constrained_find(const Other &key, std::size_t bucket) { + for(auto it = begin(bucket), last = end(bucket); it != last; ++it) { + if(packed.second()(it->first, key)) { + return begin() + static_cast(it.index()); } - } else { - static_assert(std::is_pointer_v); - using data_type = std::remove_cv_t>; - - if constexpr(std::is_array_v) { - using underlying_type = std::remove_extent_t; - accepted = index.can_cast() && (value.can_cast() || value.convert()); + } - if(accepted) { - (*Data)[index.cast()] = value.cast(); - } - } else { - accepted = value.can_cast() || value.convert(); + return end(); + } - if(accepted) { - *Data = value.cast(); - } + template + [[nodiscard]] auto constrained_find(const Other &key, std::size_t bucket) const { + for(auto it = cbegin(bucket), last = cend(bucket); it != last; ++it) { + if(packed.second()(it->first, key)) { + return cbegin() + static_cast(it.index()); } } + + return cend(); } - return accepted; -} + 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 std::make_pair(it, false); + } -template -inline meta_any getter([[maybe_unused]] meta_handle handle, [[maybe_unused]] meta_any index) { - if constexpr(std::is_function_v> || std::is_member_function_pointer_v) { - static_assert(std::is_invocable_v); - auto *clazz = handle.try_cast(); - return clazz ? std::invoke(Data, clazz) : meta_any{}; - } else if constexpr(std::is_member_object_pointer_v) { - using data_type = std::remove_cv_t().*Data)>>; - static_assert(std::is_invocable_v); - auto *clazz = handle.try_cast(); + packed.first().emplace_back(sparse.first()[index], std::piecewise_construct, std::forward_as_tuple(std::forward(key)), std::forward_as_tuple(std::forward(args)...)); + sparse.first()[index] = packed.first().size() - 1u; + rehash_if_required(); - if constexpr(std::is_array_v) { - return (clazz && index.can_cast()) ? std::invoke(Data, clazz)[index.cast()] : meta_any{}; - } else { - return clazz ? std::invoke(Data, clazz) : meta_any{}; - } - } else { - static_assert(std::is_pointer_v); + return std::make_pair(--end(), true); + } - if constexpr(std::is_array_v>) { - return index.can_cast() ? (*Data)[index.cast()] : meta_any{}; - } else { - return *Data; + 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 = std::forward(value); + return std::make_pair(it, false); } + + packed.first().emplace_back(sparse.first()[index], std::forward(key), std::forward(value)); + sparse.first()[index] = packed.first().size() - 1u; + rehash_if_required(); + + return std::make_pair(--end(), true); } -} + void move_and_pop(const std::size_t pos) { + if(const auto last = size() - 1u; pos != last) { + packed.first()[pos] = std::move(packed.first().back()); + size_type *curr = sparse.first().data() + key_to_bucket(packed.first().back().element.first); + for(; *curr != last; curr = &packed.first()[*curr].next) {} + *curr = pos; + } -template -std::enable_if_t>, meta_any> -invoke(const meta_handle &, meta_any *args, std::index_sequence) { - using helper_type = meta_function_helper>; - meta_any any{}; + packed.first().pop_back(); + } - if((((args+Indexes)->can_cast>() - || (args+Indexes)->convert>()) && ...)) - { - if constexpr(std::is_void_v) { - std::invoke(Func, (args+Indexes)->cast>()...); - } else { - any = meta_any{std::invoke(Func, (args+Indexes)->cast>()...)}; + void rehash_if_required() { + if(size() > (bucket_count() * max_load_factor())) { + rehash(bucket_count() * 2u); } } - return any; -} +public: + /*! @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 = std::pair; + /*! @brief Unsigned integer type. */ + using size_type = std::size_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 Allocator type. */ + using allocator_type = Allocator; + /*! @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) {} -template -std::enable_if_t, meta_any> -invoke(meta_handle &handle, meta_any *args, std::index_sequence) { - using helper_type = meta_function_helper>; - static_assert(std::is_base_of_v); - auto *clazz = handle.try_cast(); - meta_any any{}; + /** + * @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} {} - if(clazz && (((args+Indexes)->can_cast>() - || (args+Indexes)->convert>()) && ...)) - { - if constexpr(std::is_void_v) { - std::invoke(Member, clazz, (args+Indexes)->cast>()...); - } else { - any = meta_any{std::invoke(Member, clazz, (args+Indexes)->cast>()...)}; - } + /** + * @brief Constructs an empty container with a given allocator and user + * supplied minimal number of buckets. + * @param bucket_count Minimal number of buckets. + * @param allocator The allocator to use. + */ + dense_map(const size_type bucket_count, const allocator_type &allocator) + : dense_map{bucket_count, hasher{}, key_equal{}, allocator} {} + + /** + * @brief Constructs an empty container with a given allocator, hash + * function and user supplied minimal number of buckets. + * @param bucket_count Minimal number of buckets. + * @param hash Hash function to use. + * @param allocator The allocator to use. + */ + dense_map(const size_type bucket_count, const hasher &hash, const allocator_type &allocator) + : dense_map{bucket_count, 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 bucket_count 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 bucket_count, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{}) + : sparse{allocator, hash}, + packed{allocator, equal}, + threshold{default_threshold} { + rehash(bucket_count); } - return any; -} + /*! @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{std::piecewise_construct, std::forward_as_tuple(other.sparse.first(), allocator), std::forward_as_tuple(other.sparse.second())}, + packed{std::piecewise_construct, std::forward_as_tuple(other.packed.first(), allocator), std::forward_as_tuple(other.packed.second())}, + threshold{other.threshold} {} -template -meta_type_node * meta_node::resolve() ENTT_NOEXCEPT { - if(!type) { - static meta_type_node node{ - {}, - nullptr, - nullptr, - std::is_void_v, - std::is_integral_v, - std::is_floating_point_v, - std::is_array_v, - std::is_enum_v, - std::is_union_v, - std::is_class_v, - std::is_pointer_v, - std::is_function_v, - std::is_member_object_pointer_v, - std::is_member_function_pointer_v, - std::extent_v, - []() -> meta_type { - return internal::meta_info>::resolve(); - }, - &destroy, - []() -> meta_type { - return &node; - } - }; + /*! @brief Default move constructor. */ + dense_map(dense_map &&) = default; - type = &node; - } + /** + * @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{std::piecewise_construct, std::forward_as_tuple(std::move(other.sparse.first()), allocator), std::forward_as_tuple(std::move(other.sparse.second()))}, + packed{std::piecewise_construct, std::forward_as_tuple(std::move(other.packed.first()), allocator), std::forward_as_tuple(std::move(other.packed.second()))}, + threshold{other.threshold} {} - return type; -} + /** + * @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 &&) = default; -} + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return sparse.first().get_allocator(); + } + /** + * @brief Returns an iterator to the beginning. + * + * The returned iterator points to the first instance of the internal array. + * 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 ENTT_NOEXCEPT { + return packed.first().begin(); + } -/** - * Internal details not to be documented. - * @endcond TURN_OFF_DOXYGEN - */ + /*! @copydoc cbegin */ + [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT { + return cbegin(); + } + /*! @copydoc begin */ + [[nodiscard]] iterator begin() ENTT_NOEXCEPT { + return packed.first().begin(); + } -} + /** + * @brief Returns an iterator to the end. + * + * The returned iterator points to the element following the last instance + * of the internal array. Attempting to dereference the returned iterator + * results in undefined behavior. + * + * @return An iterator to the element following the last instance of the + * internal array. + */ + [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { + return packed.first().end(); + } + /*! @copydoc cend */ + [[nodiscard]] const_iterator end() const ENTT_NOEXCEPT { + return cend(); + } + + /*! @copydoc end */ + [[nodiscard]] iterator end() ENTT_NOEXCEPT { + return packed.first().end(); + } -#endif // ENTT_META_META_HPP + /** + * @brief Checks whether a container is empty. + * @return True if the container is empty, false otherwise. + */ + [[nodiscard]] bool empty() const ENTT_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 ENTT_NOEXCEPT { + return packed.first().size(); + } + /*! @brief Clears the container. */ + void clear() ENTT_NOEXCEPT { + sparse.first().clear(); + packed.first().clear(); + rehash(0u); + } -namespace entt { + /** + * @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. + */ + std::pair insert(const value_type &value) { + return insert_or_do_nothing(value.first, value.second); + } + /*! @copydoc insert */ + std::pair insert(value_type &&value) { + return insert_or_do_nothing(std::move(value.first), std::move(value.second)); + } -template -class meta_factory; + /** + * @copydoc insert + * @tparam Arg Type of the key-value pair to insert into the container. + */ + template + std::enable_if_t, std::pair> + insert(Arg &&value) { + return insert_or_do_nothing(std::forward(value).first, std::forward(value).second); + } + /** + * @brief Inserts elements into the container, if their keys do not exist. + * @tparam It Type of input iterator. + * @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. + */ + template + void insert(It first, It last) { + for(; first != last; ++first) { + insert(*first); + } + } -template -meta_factory reflect(const char *str, Property &&... property) ENTT_NOEXCEPT; + /** + * @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 + std::pair insert_or_assign(const key_type &key, Arg &&value) { + return insert_or_overwrite(key, std::forward(value)); + } + /*! @copydoc insert_or_assign */ + template + std::pair insert_or_assign(key_type &&key, Arg &&value) { + return insert_or_overwrite(std::move(key), std::forward(value)); + } -template -bool unregister() ENTT_NOEXCEPT; + /** + * @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 + std::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(std::forward(args).first..., std::forward(args).second...); + } else if constexpr(sizeof...(Args) == 2u) { + return insert_or_do_nothing(std::forward(args)...); + } else { + auto &node = packed.first().emplace_back(packed.first().size(), std::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 std::make_pair(it, false); + } -/** - * @brief A meta factory to be used for reflection purposes. - * - * A meta factory is an utility class used to reflect types, data and functions - * of all sorts. This class ensures that the underlying web of types is built - * correctly and performs some checks in debug mode to ensure that there are no - * subtle errors at runtime. - * - * @tparam Type Reflected type for which the factory was created. - */ -template -class meta_factory { - static_assert(std::is_object_v && !(std::is_const_v || std::is_volatile_v)); + std::swap(node.next, sparse.first()[index]); + rehash_if_required(); - template - inline bool duplicate(const hashed_string &name, const Node *node) ENTT_NOEXCEPT { - return node ? node->name == name || duplicate(name, node->next) : false; + return std::make_pair(--end(), true); + } } - inline bool duplicate(const meta_any &key, const internal::meta_prop_node *node) ENTT_NOEXCEPT { - return node ? node->key() == key || duplicate(key, node->next) : false; + /** + * @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 + std::pair try_emplace(const key_type &key, Args &&...args) { + return insert_or_do_nothing(key, std::forward(args)...); } - template - internal::meta_prop_node * properties() { - return nullptr; + /*! @copydoc try_emplace */ + template + std::pair try_emplace(key_type &&key, Args &&...args) { + return insert_or_do_nothing(std::move(key), std::forward(args)...); } - template - internal::meta_prop_node * properties(Property &&property, Other &&... other) { - static std::remove_cv_t> prop{}; - - static internal::meta_prop_node node{ - nullptr, - []() -> meta_any { - return std::get<0>(prop); - }, - []() -> meta_any { - return std::get<1>(prop); - }, - []() -> meta_prop { - return &node; - } - }; - - prop = std::forward(property); - node.next = properties(std::forward(other)...); - ENTT_ASSERT(!duplicate(meta_any{std::get<0>(prop)}, node.next)); - return &node; + /** + * @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; } - template - meta_factory type(hashed_string name, Property &&... property) ENTT_NOEXCEPT { - static internal::meta_type_node node{ - {}, - nullptr, - nullptr, - std::is_void_v, - std::is_integral_v, - std::is_floating_point_v, - std::is_array_v, - std::is_enum_v, - std::is_union_v, - std::is_class_v, - std::is_pointer_v, - std::is_function_v, - std::is_member_object_pointer_v, - std::is_member_function_pointer_v, - std::extent_v, - []() -> meta_type { - return internal::meta_info>::resolve(); - }, - &internal::destroy, - []() -> meta_type { - return &node; - } - }; + /** + * @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(); - node.name = name; - node.next = internal::meta_info<>::type; - node.prop = properties(std::forward(property)...); - ENTT_ASSERT(!duplicate(name, node.next)); - ENTT_ASSERT(!internal::meta_info::type); - internal::meta_info::type = &node; - internal::meta_info<>::type = &node; + for(auto from = last - cbegin(); from != dist; --from) { + erase(packed.first()[from - 1u].element.first); + } - return *this; + return (begin() + dist); } - void unregister_prop(internal::meta_prop_node **prop) { - while(*prop) { - auto *node = *prop; - *prop = node->next; - node->next = nullptr; + /** + * @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().data() + key_to_bucket(key); *curr != (std::numeric_limits::max)(); 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; + } } - } - void unregister_dtor() { - if(auto node = internal::meta_info::type->dtor; node) { - internal::meta_info::type->dtor = nullptr; - *node->underlying = nullptr; - } + return 0u; } - template - auto unregister_all(int) - -> decltype((internal::meta_info::type->*Member)->prop, void()) { - while(internal::meta_info::type->*Member) { - auto node = internal::meta_info::type->*Member; - internal::meta_info::type->*Member = node->next; - unregister_prop(&node->prop); - node->next = nullptr; - *node->underlying = nullptr; - } + /** + * @brief Exchanges the contents with those of a given container. + * @param other Container to exchange the content with. + */ + void swap(dense_map &other) { + using std::swap; + swap(sparse, other.sparse); + swap(packed, other.packed); + swap(threshold, other.threshold); } - template - void unregister_all(char) { - while(internal::meta_info::type->*Member) { - auto node = internal::meta_info::type->*Member; - internal::meta_info::type->*Member = node->next; - node->next = nullptr; - *node->underlying = nullptr; - } + /** + * @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; } - bool unregister() ENTT_NOEXCEPT { - const auto registered = internal::meta_info::type; - - if(registered) { - if(auto *curr = internal::meta_info<>::type; curr == internal::meta_info::type) { - internal::meta_info<>::type = internal::meta_info::type->next; - } else { - while(curr && curr->next != internal::meta_info::type) { - curr = curr->next; - } - - if(curr) { - curr->next = internal::meta_info::type->next; - } - } + /*! @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; + } - unregister_prop(&internal::meta_info::type->prop); - unregister_all<&internal::meta_type_node::base>(0); - unregister_all<&internal::meta_type_node::conv>(0); - unregister_all<&internal::meta_type_node::ctor>(0); - unregister_all<&internal::meta_type_node::data>(0); - unregister_all<&internal::meta_type_node::func>(0); - unregister_dtor(); + /** + * @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; + } - internal::meta_info::type->name = {}; - internal::meta_info::type->next = nullptr; - internal::meta_info::type = nullptr; - } + /** + * @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(std::move(key)).first->second; + } - return registered; + /** + * @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)); } - meta_factory() ENTT_NOEXCEPT = default; + /*! @copydoc find */ + [[nodiscard]] const_iterator find(const key_type &key) const { + return constrained_find(key, key_to_bucket(key)); + } -public: - template - friend meta_factory reflect(const char *str, Property &&... property) ENTT_NOEXCEPT; + /** + * @brief Finds an element with a key that compares _equivalent_ to a given + * value. + * @tparam Other Type of the key value of an element to search for. + * @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. + */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + find(const Other &key) { + return constrained_find(key, key_to_bucket(key)); + } + /*! @copydoc find */ template - friend bool unregister() ENTT_NOEXCEPT; + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + find(const Other &key) const { + return constrained_find(key, key_to_bucket(key)); + } /** - * @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. + * @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. */ - template - meta_factory base() ENTT_NOEXCEPT { - static_assert(std::is_base_of_v); - auto * const type = internal::meta_info::resolve(); - - static internal::meta_base_node node{ - &internal::meta_info::template base, - type, - nullptr, - &internal::meta_info::resolve, - [](void *instance) -> void * { - return static_cast(static_cast(instance)); - }, - []() -> meta_base { - return &node; - } - }; + [[nodiscard]] bool contains(const key_type &key) const { + return (find(key) != cend()); + } - node.next = type->base; - ENTT_ASSERT((!internal::meta_info::template base)); - internal::meta_info::template base = &node; - type->base = &node; + /** + * @brief Checks if the container contains an element with a key that + * compares _equivalent_ to a given value. + * @tparam Other Type of the key value of an element to search for. + * @param key Key value of an element to search for. + * @return True if there is such an element, false otherwise. + */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + contains(const Other &key) const { + return (find(key) != cend()); + } - return *this; + /** + * @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().begin(), sparse.first()[index]}; } /** - * @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. + * @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. */ - template - meta_factory conv() ENTT_NOEXCEPT { - static_assert(std::is_convertible_v); - auto * const type = internal::meta_info::resolve(); + [[nodiscard]] const_local_iterator begin(const size_type index) const { + return cbegin(index); + } - static internal::meta_conv_node node{ - &internal::meta_info::template conv, - type, - nullptr, - &internal::meta_info::resolve, - [](void *instance) -> meta_any { - return static_cast(*static_cast(instance)); - }, - []() -> meta_conv { - return &node; - } - }; + /** + * @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().begin(), sparse.first()[index]}; + } - node.next = type->conv; - ENTT_ASSERT((!internal::meta_info::template conv)); - internal::meta_info::template conv = &node; - type->conv = &node; + /** + * @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 {packed.first().begin(), (std::numeric_limits::max)()}; + } - return *this; + /** + * @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([[maybe_unused]] const size_type index) const { + return cend(index); } /** - * @brief Assigns a meta constructor to a meta type. - * - * Free functions 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 a free function. - * - * @tparam Func The actual function to use as a constructor. - * @tparam Property Types of properties to assign to the meta data. - * @param property Properties to assign to the meta data. - * @return A meta factory for the parent type. + * @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. */ - template - meta_factory ctor(Property &&... property) ENTT_NOEXCEPT { - using helper_type = internal::meta_function_helper>; - static_assert(std::is_same_v); - auto * const type = internal::meta_info::resolve(); + [[nodiscard]] local_iterator end([[maybe_unused]] const size_type index) { + return {packed.first().begin(), (std::numeric_limits::max)()}; + } - static internal::meta_ctor_node node{ - &internal::meta_info::template ctor, - type, - nullptr, - nullptr, - helper_type::size, - &helper_type::arg, - [](meta_any * const any) { - return internal::invoke(nullptr, any, std::make_index_sequence{}); - }, - []() -> meta_ctor { - return &node; - } - }; + /** + * @brief Returns the number of buckets. + * @return The number of buckets. + */ + [[nodiscard]] size_type bucket_count() const { + return sparse.first().size(); + } - node.next = type->ctor; - node.prop = properties(std::forward(property)...); - ENTT_ASSERT((!internal::meta_info::template ctor)); - internal::meta_info::template ctor = &node; - type->ctor = &node; + /** + * @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(); + } - return *this; + /** + * @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(std::distance(begin(index), end(index))); } /** - * @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. - * @tparam Property Types of properties to assign to the meta data. - * @param property Properties to assign to the meta data. - * @return A meta factory for the parent type. + * @brief Returns the bucket for a given key. + * @param key The value of the key to examine. + * @return The bucket for the given key. */ - template - meta_factory ctor(Property &&... property) ENTT_NOEXCEPT { - using helper_type = internal::meta_function_helper; - auto * const type = internal::meta_info::resolve(); + [[nodiscard]] size_type bucket(const key_type &key) const { + return key_to_bucket(key); + } - static internal::meta_ctor_node node{ - &internal::meta_info::template ctor, - type, - nullptr, - nullptr, - helper_type::size, - &helper_type::arg, - [](meta_any * const any) { - return internal::construct(any, std::make_index_sequence{}); - }, - []() -> meta_ctor { - return &node; - } - }; + /** + * @brief Returns the average number of elements per bucket. + * @return The average number of elements per bucket. + */ + [[nodiscard]] float load_factor() const { + return size() / static_cast(bucket_count()); + } - node.next = type->ctor; - node.prop = properties(std::forward(property)...); - ENTT_ASSERT((!internal::meta_info::template ctor)); - internal::meta_info::template ctor = &node; - type->ctor = &node; + /** + * @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; + } - return *this; + /** + * @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 Assigns a meta destructor to a meta type. - * - * Free functions can be assigned to meta types in the role of destructors. - * The signature of the function should identical to the following: - * - * @code{.cpp} - * void(Type *); - * @endcode - * - * From a client's point of view, nothing changes if the destructor of a - * meta type is the default one or a custom one. - * - * @tparam Func The actual function to use as a destructor. - * @return A meta factory for the parent type. + * @brief Reserves at least the specified number of buckets and regenerates + * the hash table. + * @param count New number of buckets. */ - template - meta_factory dtor() ENTT_NOEXCEPT { - static_assert(std::is_invocable_v); - auto * const type = internal::meta_info::resolve(); - - static internal::meta_dtor_node node{ - &internal::meta_info::template dtor, - type, - [](meta_handle handle) { - return handle.type() == internal::meta_info::resolve()->meta() - ? ((*Func)(static_cast(handle.data())), true) - : false; - }, - []() -> meta_dtor { - return &node; - } - }; + void rehash(const size_type count) { + auto value = (std::max)(count, minimum_capacity); + value = (std::max)(value, static_cast(size() / max_load_factor())); - ENTT_ASSERT(!internal::meta_info::type->dtor); - ENTT_ASSERT((!internal::meta_info::template dtor)); - internal::meta_info::template dtor = &node; - internal::meta_info::type->dtor = &node; + if(const auto sz = next_power_of_two(value); sz != bucket_count()) { + sparse.first().resize(sz); + std::fill(sparse.first().begin(), sparse.first().end(), (std::numeric_limits::max)()); - return *this; + for(size_type pos{}, last = size(); pos < last; ++pos) { + const auto index = key_to_bucket(packed.first()[pos].element.first); + packed.first()[pos].next = std::exchange(sparse.first()[index], pos); + } + } } /** - * @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 Property Types of properties to assign to the meta data. - * @param str The name to assign to the meta data. - * @param property Properties to assign to the meta data. - * @return A meta factory for the parent type. + * @brief Reserves space for at least the specified number of elements and + * regenerates the hash table. + * @param count New number of elements. */ - template - meta_factory data(const char *str, Property &&... property) ENTT_NOEXCEPT { - auto * const type = internal::meta_info::resolve(); - internal::meta_data_node *curr = nullptr; - - if constexpr(std::is_same_v) { - static internal::meta_data_node node{ - &internal::meta_info::template data, - {}, - type, - nullptr, - nullptr, - true, - true, - &internal::meta_info::resolve, - [](meta_handle, meta_any, meta_any) { return false; }, - [](meta_handle, meta_any) -> meta_any { return Data; }, - []() -> meta_data { - return &node; - } - }; + void reserve(const size_type count) { + packed.first().reserve(count); + rehash(static_cast(std::ceil(count / max_load_factor()))); + } - node.prop = properties>(std::forward(property)...); - curr = &node; - } else if constexpr(std::is_member_object_pointer_v) { - using data_type = std::remove_reference_t().*Data)>; + /** + * @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(); + } - static internal::meta_data_node node{ - &internal::meta_info::template data, - {}, - type, - nullptr, - nullptr, - std::is_const_v, - !std::is_member_object_pointer_v, - &internal::meta_info::resolve, - &internal::setter, Type, Data>, - &internal::getter, - []() -> meta_data { - return &node; - } - }; + /** + * @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(); + } - node.prop = properties>(std::forward(property)...); - curr = &node; - } else { - static_assert(std::is_pointer_v); - using data_type = std::remove_pointer_t; +private: + compressed_pair sparse; + compressed_pair packed; + float threshold; +}; - static internal::meta_data_node node{ - &internal::meta_info::template data, - {}, - type, - nullptr, - nullptr, - std::is_const_v, - !std::is_member_object_pointer_v, - &internal::meta_info::resolve, - &internal::setter, Type, Data>, - &internal::getter, - []() -> meta_data { - return &node; - } - }; +} // namespace entt - node.prop = properties>(std::forward(property)...); - curr = &node; - } +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ - curr->name = hashed_string{str}; - curr->next = type->data; - ENTT_ASSERT(!duplicate(hashed_string{str}, curr->next)); - ENTT_ASSERT((!internal::meta_info::template data)); - internal::meta_info::template data = curr; - type->data = curr; +namespace std { - return *this; - } +template +struct uses_allocator, Allocator> + : std::true_type {}; - /** - * @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 pointer 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 Property Types of properties to assign to the meta data. - * @param str The name to assign to the meta data. - * @param property Properties to assign to the meta data. - * @return A meta factory for the parent type. - */ - template - meta_factory data(const char *str, Property &&... property) ENTT_NOEXCEPT { - using owner_type = std::tuple, std::integral_constant>; - using underlying_type = std::invoke_result_t; - static_assert(std::is_invocable_v); - auto * const type = internal::meta_info::resolve(); +} // namespace std - static internal::meta_data_node node{ - &internal::meta_info::template data, - {}, - type, - nullptr, - nullptr, - false, - false, - &internal::meta_info::resolve, - &internal::setter, - &internal::getter, - []() -> meta_data { - return &node; - } - }; +/** + * Internal details not to be documented. + * @endcond + */ - node.name = hashed_string{str}; - node.next = type->data; - node.prop = properties(std::forward(property)...); - ENTT_ASSERT(!duplicate(hashed_string{str}, node.next)); - ENTT_ASSERT((!internal::meta_info::template data)); - internal::meta_info::template data = &node; - type->data = &node; +#endif - return *this; - } +// #include "../core/compressed_pair.hpp" +#ifndef ENTT_CORE_COMPRESSED_PAIR_HPP +#define ENTT_CORE_COMPRESSED_PAIR_HPP - /** - * @brief Assigns a meta funcion 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 Func The actual function to attach to the meta type. - * @tparam Property Types of properties to assign to the meta function. - * @param str The name to assign to the meta function. - * @param property Properties to assign to the meta function. - * @return A meta factory for the parent type. - */ - template - meta_factory func(const char *str, Property &&... property) ENTT_NOEXCEPT { - using owner_type = std::integral_constant; - using func_type = internal::meta_function_helper>; - auto * const type = internal::meta_info::resolve(); +#include +#include +#include +#include +// #include "../config/config.h" - static internal::meta_func_node node{ - &internal::meta_info::template func, - {}, - type, - nullptr, - nullptr, - func_type::size, - func_type::is_const, - func_type::is_static, - &internal::meta_info::resolve, - &func_type::arg, - [](meta_handle handle, meta_any *any) { - return internal::invoke(handle, any, std::make_index_sequence{}); - }, - []() -> meta_func { - return &node; - } - }; +// #include "type_traits.hpp" +#ifndef ENTT_CORE_TYPE_TRAITS_HPP +#define ENTT_CORE_TYPE_TRAITS_HPP - node.name = hashed_string{str}; - node.next = type->func; - node.prop = properties(std::forward(property)...); - ENTT_ASSERT(!duplicate(hashed_string{str}, node.next)); - ENTT_ASSERT((!internal::meta_info::template func)); - internal::meta_info::template func = &node; - type->func = &node; +#include +#include +#include +#include +// #include "../config/config.h" + +// #include "fwd.hpp" - return *this; - } -}; +namespace entt { /** - * @brief Basic function to use for reflection. - * - * This is the point from which everything starts.
- * By invoking this function with a type that is not yet reflected, a meta type - * is created to which it will be possible to attach data and functions through - * a dedicated factory. - * - * @tparam Type Type to reflect. - * @tparam Property Types of properties to assign to the reflected type. - * @param str The name to assign to the reflected type. - * @param property Properties to assign to the reflected type. - * @return A meta factory for the given type. + * @brief Utility class to disambiguate overloaded functions. + * @tparam N Number of choices available. */ -template -inline meta_factory reflect(const char *str, Property &&... property) ENTT_NOEXCEPT { - return meta_factory{}.type(hashed_string{str}, std::forward(property)...); -} +template +struct choice_t + // Unfortunately, doxygen cannot parse such a construct. + : /*! @cond TURN_OFF_DOXYGEN */ 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 Basic function to use for reflection. + * @brief Identity type trait. * - * This is the point from which everything starts.
- * By invoking this function with a type that is not yet reflected, a meta type - * is created to which it will be possible to attach data and functions through - * a dedicated factory. + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. * - * @tparam Type Type to reflect. - * @return A meta factory for the given type. + * @tparam Type A type. */ template -inline meta_factory reflect() ENTT_NOEXCEPT { - return meta_factory{}; -} - +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; /** - * @brief Basic function to unregister a type. - * - * This function unregisters 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 unregistered but the link between the two types is - * removed. - * - * @tparam Type Type to unregister. - * @return True if the type to unregister exists, false otherwise. + * @brief Helper type. + * @tparam Type A type. */ template -inline bool unregister() ENTT_NOEXCEPT { - return meta_factory().unregister(); -} +using type_identity_t = typename type_identity::type; + +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; /** - * @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. + * @brief Helper variable template. + * @tparam Type The type of which to return the size. */ template -inline meta_type resolve() ENTT_NOEXCEPT { - return internal::meta_info::resolve()->meta(); -} +inline constexpr std::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 Returns the meta type associated with a given name. - * @param str The name to use to search for a meta type. - * @return The meta type associated with the given name, if any. + * @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. */ -inline meta_type resolve(const char *str) ENTT_NOEXCEPT { - const auto *curr = internal::find_if([name = hashed_string{str}](auto *node) { - return node->name == name; - }, internal::meta_info<>::type); +template +inline constexpr auto unpack_as_value = Value; - return curr ? curr->meta() : meta_type{}; -} +/** + * @brief Wraps a static constant. + * @tparam Value A static constant. + */ +template +using integral_constant = std::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 Iterates all the reflected types. - * @tparam Op Type of the function object to invoke. - * @param op A valid function object. + * @brief A class to use to push around lists of types, nothing more. + * @tparam Type Types provided by the type list. */ -template -void resolve(Op op) ENTT_NOEXCEPT { - internal::iterate([op = std::move(op)](auto *node) { - op(node->meta()); - }, internal::meta_info<>::type); -} +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 Type 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 Type 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 = Type; +}; -#endif // ENTT_META_FACTORY_HPP +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; -// #include "meta/meta.hpp" +/** + * @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 +constexpr type_list operator+(type_list, type_list) { + return {}; +} -// #include "process/process.hpp" -#ifndef ENTT_PROCESS_PROCESS_HPP -#define ENTT_PROCESS_PROCESS_HPP +/*! @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<>; +}; -#include -#include -// #include "../config/config.h" -#ifndef ENTT_CONFIG_CONFIG_H -#define ENTT_CONFIG_CONFIG_H +/** + * @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 = typename 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; +}; -#ifndef ENTT_NOEXCEPT -#define ENTT_NOEXCEPT noexcept -#endif // ENTT_NOEXCEPT +/** + * @brief Helper type. + * @tparam List Type lists to concatenate. + */ +template +using type_list_cat_t = typename type_list_cat::type; +/*! @brief Primary template isn't defined on purpose. */ +template +struct type_list_unique; -#ifndef ENTT_HS_SUFFIX -#define ENTT_HS_SUFFIX _hs -#endif // ENTT_HS_SUFFIX +/** + * @brief Removes duplicates types from a type list. + * @tparam Type One of the types provided by the given type list. + * @tparam Other The other types provided by the given type list. + */ +template +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = std::conditional_t< + (std::is_same_v || ...), + typename type_list_unique>::type, + type_list_cat_t, typename type_list_unique>::type>>; +}; +/*! @brief Removes duplicates types from a type list. */ +template<> +struct type_list_unique> { + /*! @brief A type list without duplicate types. */ + using type = type_list<>; +}; -#ifndef ENTT_NO_ATOMIC -#include -template -using maybe_atomic_t = std::atomic; -#else // ENTT_NO_ATOMIC +/** + * @brief Helper type. + * @tparam Type A type list. + */ template -using maybe_atomic_t = Type; -#endif // ENTT_NO_ATOMIC +using type_list_unique_t = typename type_list_unique::type; +/** + * @brief Provides the member constant `value` 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; -#ifndef ENTT_ID_TYPE -#include -#define ENTT_ID_TYPE std::uint32_t -#endif // ENTT_ID_TYPE +/** + * @copybrief type_list_contains + * @tparam Type Types provided by the type list. + * @tparam Other Type to look for. + */ +template +struct type_list_contains, Other>: std::disjunction...> {}; +/** + * @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>...>; +}; -#ifndef ENTT_PAGE_SIZE -#define ENTT_PAGE_SIZE 32768 -#endif // ENTT_PAGE_SIZE +/** + * @brief Helper type. + * @tparam List Type lists between which to compute the difference. + */ +template +using type_list_diff_t = typename type_list_diff::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); +}; -#ifndef ENTT_DISABLE_ASSERT -#include -#define ENTT_ASSERT(condition) assert(condition) -#else // ENTT_DISABLE_ASSERT -#define ENTT_ASSERT(...) ((void)0) -#endif // ENTT_DISABLE_ASSERT +/*! @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> {}; -#endif // ENTT_CONFIG_CONFIG_H +/** + * @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 value. */ + static constexpr auto value = Value; +}; +/** + * @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 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 +constexpr value_list operator+(value_list, value_list) { + return {}; +} -namespace entt { +/*! @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 Base class for processes. - * - * This class stays true to the CRTP idiom. Derived classes must specify what's - * the intended type for elapsed times.
- * A process should expose publicly the following member functions whether - * required: - * - * * @code{.cpp} - * void update(Delta, void *); - * @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 init(); - * @endcode - * - * It's invoked when the process joins the running queue of a scheduler. This - * happens as soon as it's attached to the scheduler if the process is a top - * level one, otherwise when it replaces its parent if the process is a - * continuation. - * - * * @code{.cpp} - * void succeeded(); - * @endcode - * - * It's invoked in case of success, immediately after an update and during the - * same tick. - * - * * @code{.cpp} - * void failed(); - * @endcode - * - * It's invoked in case of errors, immediately after an update and during the - * same tick. - * - * * @code{.cpp} - * void aborted(); - * @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` protected member functions and even pause or unpause the - * process itself. - * - * @sa scheduler - * - * @tparam Derived Actual type of process that extends the class template. - * @tparam Delta Type to use to provide elapsed time. + * @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 = typename 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 = typename value_list_cat::type; + +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::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 process { - enum class state: unsigned int { - UNINITIALIZED = 0, - RUNNING, - PAUSED, - SUCCEEDED, - FAILED, - ABORTED, - FINISHED - }; +template class Tuple, typename... Args> +struct is_applicable>: std::is_invocable {}; - template - using state_value_t = std::integral_constant; +/** + * @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>: std::is_invocable {}; - template - auto tick(int, state_value_t) - -> decltype(std::declval().init()) { - static_cast(this)->init(); - } +/** + * @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; - template - auto tick(int, state_value_t, Delta delta, void *data) - -> decltype(std::declval().update(delta, data)) { - static_cast(this)->update(delta, data); - } +/*! @brief Same as std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::false_type {}; - template - auto tick(int, state_value_t) - -> decltype(std::declval().succeeded()) { - static_cast(this)->succeeded(); - } +/** + * @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>: std::is_invocable_r {}; - template - auto tick(int, state_value_t) - -> decltype(std::declval().failed()) { - static_cast(this)->failed(); - } +/** + * @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; - template - auto tick(int, state_value_t) - -> decltype(std::declval().aborted()) { - static_cast(this)->aborted(); - } +/** + * @brief Provides the member constant `value` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; - template - void tick(char, state_value_t, Args &&...) const ENTT_NOEXCEPT {} +/*! @copydoc is_complete */ +template +struct is_complete>: std::true_type {}; -protected: - /** - * @brief Terminates a process with success if it's still alive. - * - * The function is idempotent and it does nothing if the process isn't - * alive. - */ - void succeed() ENTT_NOEXCEPT { - if(alive()) { - current = state::SUCCEEDED; - } - } +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_complete_v = is_complete::value; - /** - * @brief Terminates a process with errors if it's still alive. - * - * The function is idempotent and it does nothing if the process isn't - * alive. - */ - void fail() ENTT_NOEXCEPT { - if(alive()) { - current = state::FAILED; - } - } +/** + * @brief Provides the member constant `value` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; - /** - * @brief Stops a process if it's in a running state. - * - * The function is idempotent and it does nothing if the process isn't - * running. - */ - void pause() ENTT_NOEXCEPT { - if(current == state::RUNNING) { - current = state::PAUSED; - } - } +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ - /** - * @brief Restarts a process if it's paused. - * - * The function is idempotent and it does nothing if the process isn't - * paused. - */ - void unpause() ENTT_NOEXCEPT { - if(current == state::PAUSED) { - current = state::RUNNING; - } - } +namespace internal { -public: - /*! @brief Type used to provide elapsed time. */ - using delta_type = Delta; +template +struct has_iterator_category: std::false_type {}; - /*! @brief Default destructor. */ - virtual ~process() ENTT_NOEXCEPT { - static_assert(std::is_base_of_v); - } +template +struct has_iterator_category::iterator_category>>: std::true_type {}; - /** - * @brief Aborts a process if it's still alive. - * - * The function is idempotent and it does nothing if the process isn't - * alive. - * - * @param immediately Requests an immediate operation. - */ - void abort(const bool immediately = false) ENTT_NOEXCEPT { - if(alive()) { - current = state::ABORTED; +} // namespace internal - if(immediately) { - tick(0); - } - } - } +/** + * Internal details not to be documented. + * @endcond + */ - /** - * @brief Returns true if a process is either running or paused. - * @return True if the process is still alive, false otherwise. - */ - bool alive() const ENTT_NOEXCEPT { - return current == state::RUNNING || current == state::PAUSED; - } +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : internal::has_iterator_category {}; - /** - * @brief Returns true if a process is already terminated. - * @return True if the process is terminated, false otherwise. - */ - bool dead() const ENTT_NOEXCEPT { - return current == state::FINISHED; - } +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_iterator_v = is_iterator::value; - /** - * @brief Returns true if a process is currently paused. - * @return True if the process is paused, false otherwise. - */ - bool paused() const ENTT_NOEXCEPT { - return current == state::PAUSED; - } +/** + * @brief Provides the member constant `value` 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 + : std::conjunction, std::negation>> {}; - /** - * @brief Returns true if a process terminated with errors. - * @return True if the process terminated with errors, false otherwise. - */ - bool rejected() const ENTT_NOEXCEPT { - return stopped; - } +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_ebco_eligible_v = is_ebco_eligible::value; - /** - * @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::UNINITIALIZED: - tick(0, state_value_t{}); - current = state::RUNNING; - break; - case state::RUNNING: - tick(0, state_value_t{}, delta, data); - break; - default: - // suppress warnings - break; - } +/** + * @brief Provides the member constant `value` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; - // if it's dead, it must be notified and removed immediately - switch(current) { - case state::SUCCEEDED: - tick(0, state_value_t{}); - current = state::FINISHED; - break; - case state::FAILED: - tick(0, state_value_t{}); - current = state::FINISHED; - stopped = true; - break; - case state::ABORTED: - tick(0, state_value_t{}); - current = state::FINISHED; - stopped = true; - break; - default: - // suppress warnings - break; - } - } +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; -private: - state current{state::UNINITIALIZED}; - bool stopped{false}; -}; +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; /** - * @brief Adaptor for lambdas and functors to turn them into processes. - * - * Lambdas and functors can't be used directly with a scheduler for they are not - * properly defined processes with managed life cycles.
- * This class helps in filling the gap and turning lambdas and functors into - * full featured processes usable by a scheduler. - * - * The signature of the function call operator should be equivalent to the - * following: - * - * @code{.cpp} - * void(Delta delta, void *data, auto succeed, auto fail); - * @endcode - * - * Where: - * - * * `delta` is the elapsed time. - * * `data` is an opaque pointer to user data if any, `nullptr` otherwise. - * * `succeed` is a function to call when a process terminates with success. - * * `fail` is a function to call when a process terminates with errors. - * - * The signature of the function call operator of both `succeed` and `fail` - * is equivalent to the following: - * - * @code{.cpp} - * void(); - * @endcode - * - * Usually users shouldn't worry about creating adaptors. A scheduler will - * create them internally each and avery time a lambda or a functor is used as - * a process. - * - * @sa process - * @sa scheduler - * - * @tparam Func Actual type of process. - * @tparam Delta Type to use to provide elapsed time. + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. */ -template -struct process_adaptor: process, Delta>, private Func { - /** - * @brief Constructs a process adaptor from a lambda or a functor. - * @tparam Args Types of arguments to use to initialize the actual process. - * @param args Parameters to use to initialize the actual process. - */ - template - process_adaptor(Args &&... args) - : Func{std::forward(args)...} - {} - /** - * @brief Updates a process and its internal state if required. - * @param delta Elapsed time. - * @param data Optional data. - */ - void update(const Delta delta, void *data) { - Func::operator()(delta, data, [this]() { this->succeed(); }, [this]() { this->fail(); }); +namespace internal { + +template +struct has_tuple_size_value: std::false_type {}; + +template +struct has_tuple_size_value::value)>>: std::true_type {}; + +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} + +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} + +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; + } +} + +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); } +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; + +/** + * @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 = std::remove_const_t; }; +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; -} +/** + * @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 = typename constness_as::type; +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); -#endif // ENTT_PROCESS_PROCESS_HPP + template + static Class *clazz(Ret (Class::*)(Args...)); -// #include "process/scheduler.hpp" -#ifndef ENTT_PROCESS_SCHEDULER_HPP -#define ENTT_PROCESS_SCHEDULER_HPP + template + static Class *clazz(Ret (Class::*)(Args...) const); + template + static Class *clazz(Type Class::*); -#include -#include -#include -#include -#include -// #include "../config/config.h" +public: + /*! @brief The class of the given non-static member object or function. */ + using type = std::remove_pointer_t()))>; +}; -// #include "process.hpp" +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; +} // namespace entt +#endif -namespace entt { +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. - * - * Example of use (pseudocode): - * - * @code{.cpp} - * scheduler.attach([](auto delta, void *, auto succeed, auto fail) { - * // code - * }).then(arguments...); - * @endcode - * - * 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. + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. */ -template -class scheduler { - struct process_handler { - using instance_type = std::unique_ptr; - using update_fn_type = bool(process_handler &, Delta, void *); - using abort_fn_type = void(process_handler &, bool); - using next_type = std::unique_ptr; - instance_type instance; - update_fn_type *update; - abort_fn_type *abort; - next_type next; - }; +namespace internal { - struct continuation { - continuation(process_handler *ref) - : handler{ref} - { - ENTT_ASSERT(handler); - } +template +struct compressed_pair_element { + using reference = Type &; + using const_reference = const Type &; - template - continuation then(Args &&... args) { - static_assert(std::is_base_of_v, Proc>); - auto proc = typename process_handler::instance_type{new Proc{std::forward(args)...}, &scheduler::deleter}; - handler->next.reset(new process_handler{std::move(proc), &scheduler::update, &scheduler::abort, nullptr}); - handler = handler->next.get(); - return *this; - } + template>> + compressed_pair_element() + : value{} {} - template - continuation then(Func &&func) { - return then, Delta>>(std::forward(func)); - } + template>, compressed_pair_element>>> + compressed_pair_element(Args &&args) + : value{std::forward(args)} {} - private: - process_handler *handler; - }; + template + compressed_pair_element(std::tuple args, std::index_sequence) + : value{std::forward(std::get(args))...} {} - template - static bool update(process_handler &handler, const Delta delta, void *data) { - auto *process = static_cast(handler.instance.get()); - process->tick(delta, data); + [[nodiscard]] reference get() ENTT_NOEXCEPT { + return value; + } - auto dead = process->dead(); + [[nodiscard]] const_reference get() const ENTT_NOEXCEPT { + return value; + } - if(dead) { - if(handler.next && !process->rejected()) { - handler = std::move(*handler.next); - // forces the process to exit the uninitialized state - dead = handler.update(handler, {}, nullptr); - } else { - handler.instance.reset(); - } - } +private: + Type value; +}; - return dead; - } +template +struct compressed_pair_element>>: Type { + using reference = Type &; + using const_reference = const Type &; + using base_type = Type; - template - static void abort(process_handler &handler, const bool immediately) { - static_cast(handler.instance.get())->abort(immediately); + template>> + compressed_pair_element() + : base_type{} {} + + template>, compressed_pair_element>>> + compressed_pair_element(Args &&args) + : base_type{std::forward(args)} {} + + template + compressed_pair_element(std::tuple args, std::index_sequence) + : base_type{std::forward(std::get(args))...} {} + + [[nodiscard]] reference get() ENTT_NOEXCEPT { + return *this; } - template - static void deleter(void *proc) { - delete static_cast(proc); + [[nodiscard]] const_reference get() const ENTT_NOEXCEPT { + return *this; } +}; -public: - /*! @brief Unsigned integer type. */ - using size_type = typename std::vector::size_type; +} // namespace internal - /*! @brief Default constructor. */ - scheduler() ENTT_NOEXCEPT = default; +/** + * Internal details not to be documented. + * @endcond + */ - /*! @brief Default move constructor. */ - scheduler(scheduler &&) = default; +/** + * @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; - /*! @brief Default move assignment operator. @return This scheduler. */ - scheduler & operator=(scheduler &&) = default; +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 Number of processes currently scheduled. - * @return Number of processes currently scheduled. + * @brief Default constructor, conditionally enabled. + * + * This constructor is only available when the types that the pair stores + * are both at least default constructible. + * + * @tparam Dummy Dummy template parameter used for internal purposes. */ - size_type size() const ENTT_NOEXCEPT { - return handlers.size(); - } + template && std::is_default_constructible_v>> + constexpr compressed_pair() + : first_base{}, + second_base{} {} /** - * @brief Returns true if at least a process is currently scheduled. - * @return True if there are scheduled processes, false otherwise. + * @brief Copy constructor. + * @param other The instance to copy from. */ - bool empty() const ENTT_NOEXCEPT { - return handlers.empty(); - } + constexpr compressed_pair(const compressed_pair &other) = default; /** - * @brief Discards all scheduled processes. - * - * Processes aren't aborted. They are discarded along with their children - * and never executed again. + * @brief Move constructor. + * @param other The instance to move from. */ - void clear() { - handlers.clear(); - } + constexpr compressed_pair(compressed_pair &&other) = default; /** - * @brief Schedules a process for the next tick. - * - * Returned value is an opaque object that can be used to attach a child to - * the given process. The child is automatically scheduled when the process - * terminates and only if the process returns with success. - * - * Example of use (pseudocode): - * - * @code{.cpp} - * // schedules a task in the form of a process class - * scheduler.attach(arguments...) - * // appends a child in the form of a lambda function - * .then([](auto delta, void *, auto succeed, auto fail) { - * // code - * }) - * // appends a child in the form of another process class - * .then(); - * @endcode - * - * @tparam Proc Type of process to schedule. - * @tparam Args Types of arguments to use to initialize the process. - * @param args Parameters to use to initialize the process. - * @return An opaque object to use to concatenate processes. + * @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 - auto attach(Args &&... args) { - static_assert(std::is_base_of_v, Proc>); - auto proc = typename process_handler::instance_type{new Proc{std::forward(args)...}, &scheduler::deleter}; - process_handler handler{std::move(proc), &scheduler::update, &scheduler::abort, nullptr}; - // forces the process to exit the uninitialized state - handler.update(handler, {}, nullptr); - return continuation{&handlers.emplace_back(std::move(handler))}; - } + template + constexpr compressed_pair(Arg &&arg, Other &&other) + : first_base{std::forward(arg)}, + second_base{std::forward(other)} {} /** - * @brief Schedules a process for the next tick. - * - * A process can be either a lambda or a functor. The scheduler wraps both - * of them in a process adaptor internally.
- * The signature of the function call operator should be equivalent to the - * following: - * - * @code{.cpp} - * void(Delta delta, void *data, auto succeed, auto fail); - * @endcode - * - * Where: - * - * * `delta` is the elapsed time. - * * `data` is an opaque pointer to user data if any, `nullptr` otherwise. - * * `succeed` is a function to call when a process terminates with success. - * * `fail` is a function to call when a process terminates with errors. - * - * The signature of the function call operator of both `succeed` and `fail` - * is equivalent to the following: - * - * @code{.cpp} - * void(); - * @endcode - * - * Returned value is an opaque object that can be used to attach a child to - * the given process. The child is automatically scheduled when the process - * terminates and only if the process returns with success. - * - * Example of use (pseudocode): - * - * @code{.cpp} - * // schedules a task in the form of a lambda function - * scheduler.attach([](auto delta, void *, auto succeed, auto fail) { - * // code - * }) - * // appends a child in the form of another lambda function - * .then([](auto delta, void *, auto succeed, auto fail) { - * // code - * }) - * // appends a child in the form of a process class - * .then(arguments...); - * @endcode - * - * @sa process_adaptor - * - * @tparam Func Type of process to schedule. - * @param func Either a lambda or a functor to use as a process. - * @return An opaque object to use to concatenate processes. + * @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 - auto attach(Func &&func) { - using Proc = process_adaptor, Delta>; - return attach(std::forward(func)); - } + template + constexpr compressed_pair(std::piecewise_construct_t, std::tuple args, std::tuple other) + : first_base{std::move(args), std::index_sequence_for{}}, + second_base{std::move(other), std::index_sequence_for{}} {} /** - * @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. + * @brief Copy assignment operator. + * @param other The instance to copy from. + * @return This compressed pair object. */ - void update(const Delta delta, void *data = nullptr) { - bool clean = false; + constexpr compressed_pair &operator=(const compressed_pair &other) = default; - for(auto pos = handlers.size(); pos; --pos) { - auto &handler = handlers[pos-1]; - const bool dead = handler.update(handler, delta, data); - clean = clean || dead; - } + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This compressed pair object. + */ + constexpr compressed_pair &operator=(compressed_pair &&other) = default; - if(clean) { - handlers.erase(std::remove_if(handlers.begin(), handlers.end(), [](auto &handler) { - return !handler.instance; - }), handlers.end()); - } + /** + * @brief Returns the first element that a pair stores. + * @return The first element that a pair stores. + */ + [[nodiscard]] first_type &first() ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /*! @copydoc first */ + [[nodiscard]] const first_type &first() const ENTT_NOEXCEPT { + return static_cast(*this).get(); } /** - * @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 immediately Requests an immediate operation. + * @brief Returns the second element that a pair stores. + * @return The second element that a pair stores. */ - void abort(const bool immediately = false) { - decltype(handlers) exec; - exec.swap(handlers); + [[nodiscard]] second_type &second() ENTT_NOEXCEPT { + return static_cast(*this).get(); + } - std::for_each(exec.begin(), exec.end(), [immediately](auto &handler) { - handler.abort(handler, immediately); - }); + /*! @copydoc second */ + [[nodiscard]] const second_type &second() const ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /** + * @brief Swaps two compressed pair objects. + * @param other The compressed pair to swap with. + */ + void swap(compressed_pair &other) { + using std::swap; + swap(first(), other.first()); + swap(second(), other.second()); + } - std::move(handlers.begin(), handlers.end(), std::back_inserter(exec)); - handlers.swap(exec); + /** + * @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 + decltype(auto) get() ENTT_NOEXCEPT { + if constexpr(Index == 0u) { + return first(); + } else { + static_assert(Index == 1u, "Index out of bounds"); + return second(); + } } -private: - std::vector handlers{}; + /*! @copydoc get */ + template + decltype(auto) get() const ENTT_NOEXCEPT { + if constexpr(Index == 0u) { + return first(); + } else { + static_assert(Index == 1u, "Index out of bounds"); + 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, std::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 +inline void swap(compressed_pair &lhs, compressed_pair &rhs) { + lhs.swap(rhs); } +} // namespace entt -#endif // ENTT_PROCESS_SCHEDULER_HPP +// disable structured binding support for clang 6, it messes when specializing tuple_size +#if !defined __clang_major__ || __clang_major__ > 6 +namespace std { -// #include "resource/cache.hpp" -#ifndef ENTT_RESOURCE_CACHE_HPP -#define ENTT_RESOURCE_CACHE_HPP +/** + * @brief `std::tuple_size` specialization for `compressed_pair`s. + * @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 +struct tuple_size>: integral_constant {}; +/** + * @brief `std::tuple_element` specialization for `compressed_pair`s. + * @tparam Index The index of the type to return. + * @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 +struct tuple_element>: conditional { + static_assert(Index < 2u, "Index out of bounds"); +}; -#include -#include -#include -#include -// #include "../config/config.h" -#ifndef ENTT_CONFIG_CONFIG_H -#define ENTT_CONFIG_CONFIG_H +} // namespace std +#endif +#endif -#ifndef ENTT_NOEXCEPT -#define ENTT_NOEXCEPT noexcept -#endif // ENTT_NOEXCEPT +// #include "../core/fwd.hpp" +#ifndef ENTT_CORE_FWD_HPP +#define ENTT_CORE_FWD_HPP +#include +#include +// #include "../config/config.h" -#ifndef ENTT_HS_SUFFIX -#define ENTT_HS_SUFFIX _hs -#endif // ENTT_HS_SUFFIX +namespace entt { -#ifndef ENTT_NO_ATOMIC -#include -template -using maybe_atomic_t = std::atomic; -#else // ENTT_NO_ATOMIC -template -using maybe_atomic_t = Type; -#endif // ENTT_NO_ATOMIC +template)> +class basic_any; +/*! @brief Alias declaration for type identifiers. */ +using id_type = ENTT_ID_TYPE; -#ifndef ENTT_ID_TYPE -#include -#define ENTT_ID_TYPE std::uint32_t -#endif // ENTT_ID_TYPE +/*! @brief Alias declaration for the most common use case. */ +using any = basic_any<>; +} // namespace entt -#ifndef ENTT_PAGE_SIZE -#define ENTT_PAGE_SIZE 32768 -#endif // ENTT_PAGE_SIZE +#endif +// #include "../core/type_info.hpp" +#ifndef ENTT_CORE_TYPE_INFO_HPP +#define ENTT_CORE_TYPE_INFO_HPP -#ifndef ENTT_DISABLE_ASSERT -#include -#define ENTT_ASSERT(condition) assert(condition) -#else // ENTT_DISABLE_ASSERT -#define ENTT_ASSERT(...) ((void)0) -#endif // ENTT_DISABLE_ASSERT +#include +#include +#include +// #include "../config/config.h" +// #include "../core/attribute.h" +#ifndef ENTT_CORE_ATTRIBUTE_H +#define ENTT_CORE_ATTRIBUTE_H + +#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 + +#endif -#endif // ENTT_CONFIG_CONFIG_H +// #include "fwd.hpp" -// #include "../core/hashed_string.hpp" +// #include "hashed_string.hpp" #ifndef ENTT_CORE_HASHED_STRING_HPP #define ENTT_CORE_HASHED_STRING_HPP - #include -// #include "../config/config.h" -#ifndef ENTT_CONFIG_CONFIG_H -#define ENTT_CONFIG_CONFIG_H - - -#ifndef ENTT_NOEXCEPT -#define ENTT_NOEXCEPT noexcept -#endif // ENTT_NOEXCEPT - - -#ifndef ENTT_HS_SUFFIX -#define ENTT_HS_SUFFIX _hs -#endif // ENTT_HS_SUFFIX - - -#ifndef ENTT_NO_ATOMIC -#include -template -using maybe_atomic_t = std::atomic; -#else // ENTT_NO_ATOMIC -template -using maybe_atomic_t = Type; -#endif // ENTT_NO_ATOMIC - - -#ifndef ENTT_ID_TYPE #include -#define ENTT_ID_TYPE std::uint32_t -#endif // ENTT_ID_TYPE - - -#ifndef ENTT_PAGE_SIZE -#define ENTT_PAGE_SIZE 32768 -#endif // ENTT_PAGE_SIZE - - -#ifndef ENTT_DISABLE_ASSERT -#include -#define ENTT_ASSERT(condition) assert(condition) -#else // ENTT_DISABLE_ASSERT -#define ENTT_ASSERT(...) ((void)0) -#endif // ENTT_DISABLE_ASSERT - - -#endif // ENTT_CONFIG_CONFIG_H +// #include "../config/config.h" +// #include "fwd.hpp" namespace entt { - /** * @cond TURN_OFF_DOXYGEN * Internal details not to be documented. */ - namespace internal { - template struct fnv1a_traits; - template<> struct fnv1a_traits { + using type = std::uint32_t; static constexpr std::uint32_t offset = 2166136261; static constexpr std::uint32_t prime = 16777619; }; - template<> struct fnv1a_traits { + using type = std::uint64_t; static constexpr std::uint64_t offset = 14695981039346656037ull; static constexpr std::uint64_t prime = 1099511628211ull; }; +template +struct basic_hashed_string { + using value_type = Char; + using size_type = std::size_t; + using hash_type = id_type; -} + const value_type *repr; + size_type length; + hash_type hash; +}; +} // namespace internal /** * Internal details not to be documented. - * @endcond TURN_OFF_DOXYGEN + * @endcond */ - /** * @brief Zero overhead unique identifier. * * A hashed string is a compile-time tool that allows users to use - * human-readable identifers in the codebase while using their numeric + * 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. */ -class hashed_string { - using traits_type = internal::fnv1a_traits; +template +class basic_hashed_string: internal::basic_hashed_string { + using base_type = internal::basic_hashed_string; + using hs_traits = internal::fnv1a_traits; struct const_wrapper { // non-explicit constructor on purpose - constexpr const_wrapper(const char *curr) ENTT_NOEXCEPT: str{curr} {} - const char *str; + constexpr const_wrapper(const Char *str) ENTT_NOEXCEPT: repr{str} {} + const Char *repr; }; // Fowler–Noll–Vo hash function v. 1a - the good - inline static constexpr ENTT_ID_TYPE helper(ENTT_ID_TYPE partial, const char *curr) ENTT_NOEXCEPT { - return curr[0] == 0 ? partial : helper((partial^curr[0])*traits_type::prime, curr+1); + [[nodiscard]] static constexpr auto helper(const Char *str) ENTT_NOEXCEPT { + base_type base{str, 0u, hs_traits::offset}; + + for(; str[base.length]; ++base.length) { + base.hash = (base.hash ^ static_cast(str[base.length])) * hs_traits::prime; + } + + return base; + } + + // Fowler–Noll–Vo hash function v. 1a - the good + [[nodiscard]] static constexpr auto helper(const Char *str, const std::size_t len) ENTT_NOEXCEPT { + base_type base{str, len, hs_traits::offset}; + + for(size_type pos{}; pos < len; ++pos) { + base.hash = (base.hash ^ static_cast(str[pos])) * hs_traits::prime; + } + + return base; } public: + /*! @brief Character type. */ + using value_type = typename base_type::value_type; /*! @brief Unsigned integer type. */ - using hash_type = ENTT_ID_TYPE; + using size_type = typename base_type::size_type; + /*! @brief Unsigned integer type. */ + using hash_type = typename base_type::hash_type; /** - * @brief Returns directly the numeric representation of a string. - * - * Forcing template resolution avoids implicit conversions. An - * human-readable identifier can be anything but a plain, old bunch of - * characters.
- * Example of use: - * @code{.cpp} - * const auto value = hashed_string::to_value("my.png"); - * @endcode - * - * @tparam N Number of characters of the identifier. - * @param str Human-readable identifer. + * @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. */ - template - inline static constexpr hash_type to_value(const char (&str)[N]) ENTT_NOEXCEPT { - return helper(traits_type::offset, str); + [[nodiscard]] static constexpr hash_type value(const value_type *str, const size_type len) ENTT_NOEXCEPT { + return basic_hashed_string{str, len}; } /** * @brief Returns directly the numeric representation of a string. - * @param wrapper Helps achieving the purpose by relying on overloading. + * @tparam N Number of characters of the identifier. + * @param str Human-readable identifier. * @return The numeric representation of the string. */ - inline static hash_type to_value(const_wrapper wrapper) ENTT_NOEXCEPT { - return helper(traits_type::offset, wrapper.str); + template + [[nodiscard]] static constexpr hash_type value(const value_type (&str)[N]) ENTT_NOEXCEPT { + return basic_hashed_string{str}; } /** - * @brief Returns directly the numeric representation of a string view. - * @param str Human-readable identifer. - * @param size Length of the string to hash. + * @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. */ - inline static hash_type to_value(const char *str, std::size_t size) ENTT_NOEXCEPT { - ENTT_ID_TYPE partial{traits_type::offset}; - while(size--) { partial = (partial^(str++)[0])*traits_type::prime; } - return partial; + [[nodiscard]] static constexpr hash_type value(const_wrapper wrapper) ENTT_NOEXCEPT { + return basic_hashed_string{wrapper}; } /*! @brief Constructs an empty hashed string. */ - constexpr hashed_string() ENTT_NOEXCEPT - : str{nullptr}, hash{} - {} + constexpr basic_hashed_string() ENTT_NOEXCEPT + : base_type{} {} /** - * @brief Constructs a hashed string from an array of const chars. - * - * Forcing template resolution avoids implicit conversions. An - * human-readable identifier can be anything but a plain, old bunch of - * characters.
- * Example of use: - * @code{.cpp} - * hashed_string hs{"my.png"}; - * @endcode - * + * @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) ENTT_NOEXCEPT + : base_type{helper(str, len)} {} + + /** + * @brief Constructs a hashed string from an array of const characters. * @tparam N Number of characters of the identifier. - * @param curr Human-readable identifer. + * @param str Human-readable identifier. */ template - constexpr hashed_string(const char (&curr)[N]) ENTT_NOEXCEPT - : str{curr}, hash{helper(traits_type::offset, curr)} - {} + constexpr basic_hashed_string(const value_type (&str)[N]) ENTT_NOEXCEPT + : base_type{helper(str)} {} /** * @brief Explicit constructor on purpose to avoid constructing a hashed - * string directly from a `const char *`. + * 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 hashed_string(const_wrapper wrapper) ENTT_NOEXCEPT - : str{wrapper.str}, hash{helper(traits_type::offset, wrapper.str)} - {} + explicit constexpr basic_hashed_string(const_wrapper wrapper) ENTT_NOEXCEPT + : base_type{helper(wrapper.repr)} {} /** - * @brief Returns the human-readable representation of a hashed string. - * @return The string used to initialize the instance. + * @brief Returns the size a hashed string. + * @return The size of the hashed string. */ - constexpr const char * data() const ENTT_NOEXCEPT { - return str; + [[nodiscard]] constexpr size_type size() const ENTT_NOEXCEPT { + return base_type::length; } /** - * @brief Returns the numeric representation of a hashed string. - * @return The numeric representation of the instance. + * @brief Returns the human-readable representation of a hashed string. + * @return The string used to initialize the hashed string. */ - constexpr hash_type value() const ENTT_NOEXCEPT { - return hash; + [[nodiscard]] constexpr const value_type *data() const ENTT_NOEXCEPT { + return base_type::repr; } /** - * @brief Returns the human-readable representation of a hashed string. - * @return The string used to initialize the instance. + * @brief Returns the numeric representation of a hashed string. + * @return The numeric representation of the hashed string. */ - constexpr operator const char *() const ENTT_NOEXCEPT { return str; } + [[nodiscard]] constexpr hash_type value() const ENTT_NOEXCEPT { + return base_type::hash; + } - /*! @copydoc value */ - constexpr operator hash_type() const ENTT_NOEXCEPT { return hash; } + /*! @copydoc data */ + [[nodiscard]] constexpr operator const value_type *() const ENTT_NOEXCEPT { + return data(); + } /** - * @brief Compares two hashed strings. - * @param other Hashed string with which to compare. - * @return True if the two hashed strings are identical, false otherwise. + * @brief Returns the numeric representation of a hashed string. + * @return The numeric representation of the hashed string. */ - constexpr bool operator==(const hashed_string &other) const ENTT_NOEXCEPT { - return hash == other.hash; + [[nodiscard]] constexpr operator hash_type() const ENTT_NOEXCEPT { + return value(); } - -private: - const char *str; - hash_type hash; }; +/** + * @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, const std::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 +basic_hashed_string(const Char (&str)[N]) -> basic_hashed_string; /** * @brief Compares two hashed strings. + * @tparam Char Character type. * @param lhs A valid hashed string. * @param rhs A valid hashed string. * @return True if the two hashed strings are identical, false otherwise. */ -constexpr bool operator!=(const hashed_string &lhs, const hashed_string &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); +template +[[nodiscard]] constexpr bool operator==(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return lhs.value() == rhs.value(); } - +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the two hashed strings differ, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator!=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); } - /** - * @brief User defined literal for hashed strings. - * @param str The literal without its suffix. - * @return A properly initialized hashed string. + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is less than the second, false otherwise. */ -constexpr entt::hashed_string operator"" ENTT_HS_SUFFIX(const char *str, std::size_t) ENTT_NOEXCEPT { - return entt::hashed_string{str}; +template +[[nodiscard]] constexpr bool operator<(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return lhs.value() < rhs.value(); } - -#endif // ENTT_CORE_HASHED_STRING_HPP - -// #include "handle.hpp" -#ifndef ENTT_RESOURCE_HANDLE_HPP -#define ENTT_RESOURCE_HANDLE_HPP - - -#include -#include -// #include "../config/config.h" - -// #include "fwd.hpp" -#ifndef ENTT_RESOURCE_FWD_HPP -#define ENTT_RESOURCE_FWD_HPP - - -// #include "../config/config.h" - - - -namespace entt { - - -/*! @class resource_cache */ -template -class resource_cache; - -/*! @class resource_handle */ -template -class resource_handle; - -/*! @class resource_loader */ -template -class resource_loader; - - +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is less than or equal to the second, false + * otherwise. + */ +template +[[nodiscard]] constexpr bool operator<=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return !(rhs < lhs); } - -#endif // ENTT_RESOURCE_FWD_HPP - - - -namespace entt { - - /** - * @brief Shared resource handle. - * - * A shared resource handle is a small class that wraps a resource and keeps it - * alive even if it's deleted from the cache. It can be either copied or - * moved. A handle shares a reference to the same resource with all the other - * handles constructed for the same identifier.
- * As a rule of thumb, resources should never be copied nor moved. Handles are - * the way to go to keep references to them. - * - * @tparam Resource Type of resource managed by a handle. + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is greater than the second, false + * otherwise. */ -template -class resource_handle { - /*! @brief Resource handles are friends of their caches. */ - friend class resource_cache; - - resource_handle(std::shared_ptr res) ENTT_NOEXCEPT - : resource{std::move(res)} - {} - -public: - /*! @brief Default constructor. */ - resource_handle() ENTT_NOEXCEPT = default; - - /** - * @brief Gets a reference to the managed resource. - * - * @warning - * The behavior is undefined if the handle doesn't contain a resource.
- * An assertion will abort the execution at runtime in debug mode if the - * handle is empty. - * - * @return A reference to the managed resource. - */ - const Resource & get() const ENTT_NOEXCEPT { - ENTT_ASSERT(static_cast(resource)); - return *resource; - } - - /*! @copydoc get */ - Resource & get() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).get()); - } - - /*! @copydoc get */ - inline operator const Resource & () const ENTT_NOEXCEPT { return get(); } - - /*! @copydoc get */ - inline operator Resource & () ENTT_NOEXCEPT { return get(); } - - /*! @copydoc get */ - inline const Resource & operator *() const ENTT_NOEXCEPT { return get(); } - - /*! @copydoc get */ - inline Resource & operator *() ENTT_NOEXCEPT { return get(); } - - /** - * @brief Gets a pointer to the managed resource. - * - * @warning - * The behavior is undefined if the handle doesn't contain a resource.
- * An assertion will abort the execution at runtime in debug mode if the - * handle is empty. - * - * @return A pointer to the managed resource or `nullptr` if the handle - * contains no resource at all. - */ - inline const Resource * operator->() const ENTT_NOEXCEPT { - ENTT_ASSERT(static_cast(resource)); - return resource.get(); - } +template +[[nodiscard]] constexpr bool operator>(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} - /*! @copydoc operator-> */ - inline Resource * operator->() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).operator->()); - } +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is greater than or equal to the second, + * false otherwise. + */ +template +[[nodiscard]] constexpr bool operator>=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} - /** - * @brief Returns true if a handle contains a resource, false otherwise. - * @return True if the handle contains a resource, false otherwise. - */ - explicit operator bool() const { return static_cast(resource); } +/*! @brief Aliases for common character types. */ +using hashed_string = basic_hashed_string; -private: - std::shared_ptr resource; -}; +/*! @brief Aliases for common character types. */ +using hashed_wstring = 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]] constexpr hashed_string operator"" _hs(const char *str, std::size_t) ENTT_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]] constexpr hashed_wstring operator"" _hws(const wchar_t *str, std::size_t) ENTT_NOEXCEPT { + return hashed_wstring{str}; +} -#endif // ENTT_RESOURCE_HANDLE_HPP - -// #include "loader.hpp" -#ifndef ENTT_RESOURCE_LOADER_HPP -#define ENTT_RESOURCE_LOADER_HPP - +} // namespace literals -#include -// #include "fwd.hpp" +} // namespace entt +#endif namespace entt { - /** - * @brief Base class for resource loaders. - * - * Resource loaders must inherit from this class and stay true to the CRTP - * idiom. Moreover, a resource loader must expose a public, const member - * function named `load` that accepts a variable number of arguments and returns - * a shared pointer to the resource just created.
- * As an example: - * - * @code{.cpp} - * struct my_resource {}; - * - * struct my_loader: entt::resource_loader { - * std::shared_ptr load(int) const { - * // use the integer value somehow - * return std::make_shared(); - * } - * }; - * @endcode - * - * In general, resource loaders should not have a state or retain data of any - * type. They should let the cache manage their resources instead. - * - * @note - * Base class and CRTP idiom aren't strictly required with the current - * implementation. One could argue that a cache can easily work with loaders of - * any type. However, future changes won't be breaking ones by forcing the use - * of a base class today and that's why the model is already in its place. - * - * @tparam Loader Type of the derived class. - * @tparam Resource Type of resource for which to use the loader. + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. */ -template -class resource_loader { - /*! @brief Resource loaders are friends of their caches. */ - friend class resource_cache; - /** - * @brief Loads the resource and returns it. - * @tparam Args Types of arguments for the loader. - * @param args Arguments for the loader. - * @return The resource just loaded or an empty pointer in case of errors. - */ - template - std::shared_ptr get(Args &&... args) const { - return static_cast(this)->load(std::forward(args)...); +namespace internal { + +struct ENTT_API type_index final { + [[nodiscard]] static id_type next() ENTT_NOEXCEPT { + static ENTT_MAYBE_ATOMIC(id_type) value{}; + return value++; } }; - +template +[[nodiscard]] constexpr auto stripped_type_name() ENTT_NOEXCEPT { +#if defined ENTT_PRETTY_FUNCTION + std::string_view pretty_function{ENTT_PRETTY_FUNCTION}; + auto first = pretty_function.find_first_not_of(' ', pretty_function.find_first_of(ENTT_PRETTY_FUNCTION_PREFIX) + 1); + auto value = pretty_function.substr(first, pretty_function.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first); + return value; +#else + return std::string_view{""}; +#endif } +template().find_first_of('.')> +[[nodiscard]] static constexpr std::string_view type_name(int) ENTT_NOEXCEPT { + constexpr auto value = stripped_type_name(); + return value; +} -#endif // ENTT_RESOURCE_LOADER_HPP - -// #include "fwd.hpp" - +template +[[nodiscard]] static std::string_view type_name(char) ENTT_NOEXCEPT { + static const auto value = stripped_type_name(); + return value; +} +template().find_first_of('.')> +[[nodiscard]] static constexpr id_type type_hash(int) ENTT_NOEXCEPT { + constexpr auto stripped = stripped_type_name(); + constexpr auto value = hashed_string::value(stripped.data(), stripped.size()); + return value; +} -namespace entt { +template +[[nodiscard]] static id_type type_hash(char) ENTT_NOEXCEPT { + static const auto value = [](const auto stripped) { + return hashed_string::value(stripped.data(), stripped.size()); + }(stripped_type_name()); + return value; +} +} // namespace internal /** - * @brief Simple cache for resources of a given type. - * - * Minimal implementation of a cache for resources of a given type. It doesn't - * offer much functionalities but it's suitable for small or medium sized - * applications and can be freely inherited to add targeted functionalities for - * large sized applications. - * - * @tparam Resource Type of resources managed by a cache. + * Internal details not to be documented. + * @endcond */ -template -class resource_cache { - using container_type = std::unordered_map>; - -public: - /*! @brief Unsigned integer type. */ - using size_type = typename container_type::size_type; - /*! @brief Type of resources managed by a cache. */ - using resource_type = typename hashed_string::hash_type; - - /*! @brief Default constructor. */ - resource_cache() = default; - - /*! @brief Default move constructor. */ - resource_cache(resource_cache &&) = default; - - /*! @brief Default move assignment operator. @return This cache. */ - resource_cache & operator=(resource_cache &&) = default; +/** + * @brief Type sequential identifier. + * @tparam Type Type for which to generate a sequential identifier. + */ +template +struct ENTT_API type_index final { /** - * @brief Number of resources managed by a cache. - * @return Number of resources currently stored. + * @brief Returns the sequential identifier of a given type. + * @return The sequential identifier of a given type. */ - size_type size() const ENTT_NOEXCEPT { - return resources.size(); + [[nodiscard]] static id_type value() ENTT_NOEXCEPT { + static const id_type value = internal::type_index::next(); + return value; } - /** - * @brief Returns true if a cache contains no resources, false otherwise. - * @return True if the cache contains no resources, false otherwise. - */ - bool empty() const ENTT_NOEXCEPT { - return resources.empty(); + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const ENTT_NOEXCEPT { + return value(); } +}; +/** + * @brief Type hash. + * @tparam Type Type for which to generate a hash value. + */ +template +struct type_hash final { /** - * @brief Clears a cache and discards all its resources. - * - * Handles are not invalidated and the memory used by a resource isn't - * freed as long as at least a handle keeps the resource itself alive. + * @brief Returns the numeric representation of a given type. + * @return The numeric representation of the given type. */ - void clear() ENTT_NOEXCEPT { - resources.clear(); +#if defined ENTT_PRETTY_FUNCTION + [[nodiscard]] static constexpr id_type value() ENTT_NOEXCEPT { + return internal::type_hash(0); +#else + [[nodiscard]] static constexpr id_type value() ENTT_NOEXCEPT { + return type_index::value(); +#endif } - /** - * @brief Loads the resource that corresponds to a given identifier. - * - * In case an identifier isn't already present in the cache, it loads its - * resource and stores it aside for future uses. Arguments are forwarded - * directly to the loader in order to construct properly the requested - * resource. - * - * @note - * If the identifier is already present in the cache, this function does - * nothing and the arguments are simply discarded. - * - * @warning - * If the resource cannot be loaded correctly, the returned handle will be - * invalid and any use of it will result in undefined behavior. - * - * @tparam Loader Type of loader to use to load the resource if required. - * @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 handle for the given resource. - */ - template - resource_handle load(const resource_type id, Args &&... args) { - static_assert(std::is_base_of_v, Loader>); - resource_handle handle{}; - - if(auto it = resources.find(id); it == resources.cend()) { - if(auto resource = Loader{}.get(std::forward(args)...); resource) { - resources[id] = resource; - handle = std::move(resource); - } - } else { - handle = it->second; - } - - return handle; + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const ENTT_NOEXCEPT { + return value(); } +}; +/** + * @brief Type name. + * @tparam Type Type for which to generate a name. + */ +template +struct type_name final { /** - * @brief Reloads a resource or loads it for the first time if not present. - * - * Equivalent to the following snippet (pseudocode): - * - * @code{.cpp} - * cache.discard(id); - * cache.load(id, args...); - * @endcode - * - * Arguments are forwarded directly to the loader in order to construct - * properly the requested resource. - * - * @warning - * If the resource cannot be loaded correctly, the returned handle will be - * invalid and any use of it will result in undefined behavior. - * - * @tparam Loader Type of loader to use to load the resource. - * @tparam Args Types of arguments to use to load the resource. - * @param id Unique resource identifier. - * @param args Arguments to use to load the resource. - * @return A handle for the given resource. + * @brief Returns the name of a given type. + * @return The name of the given type. */ - template - resource_handle reload(const resource_type id, Args &&... args) { - return (discard(id), load(id, std::forward(args)...)); + [[nodiscard]] static constexpr std::string_view value() ENTT_NOEXCEPT { + return internal::type_name(0); + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator std::string_view() const ENTT_NOEXCEPT { + return value(); } +}; +/*! @brief Implementation specific information about a type. */ +struct type_info final { /** - * @brief Creates a temporary handle for a resource. - * - * Arguments are forwarded directly to the loader in order to construct - * properly the requested resource. The handle isn't stored aside and the - * cache isn't in charge of the lifetime of the resource itself. - * - * @tparam Loader Type of loader to use to load the resource. - * @tparam Args Types of arguments to use to load the resource. - * @param args Arguments to use to load the resource. - * @return A handle for the given resource. + * @brief Constructs a type info object for a given type. + * @tparam Type Type for which to construct a type info object. */ - template - resource_handle temp(Args &&... args) const { - return { Loader{}.get(std::forward(args)...) }; - } + template + constexpr type_info(std::in_place_type_t) ENTT_NOEXCEPT + : seq{type_index>>::value()}, + identifier{type_hash>>::value()}, + alias{type_name>>::value()} {} /** - * @brief Creates a handle for a given resource identifier. - * - * A resource handle can be in a either valid or invalid state. In other - * terms, a resource handle is properly initialized with a resource if the - * cache contains the resource itself. Otherwise the returned handle is - * uninitialized and accessing it results in undefined behavior. - * - * @sa resource_handle - * - * @param id Unique resource identifier. - * @return A handle for the given resource. + * @brief Type index. + * @return Type index. */ - resource_handle handle(const resource_type id) const { - auto it = resources.find(id); - return { it == resources.end() ? nullptr : it->second }; + [[nodiscard]] constexpr id_type index() const ENTT_NOEXCEPT { + return seq; } /** - * @brief Checks if a cache contains a given identifier. - * @param id Unique resource identifier. - * @return True if the cache contains the resource, false otherwise. + * @brief Type hash. + * @return Type hash. */ - bool contains(const resource_type id) const ENTT_NOEXCEPT { - return (resources.find(id) != resources.cend()); + [[nodiscard]] constexpr id_type hash() const ENTT_NOEXCEPT { + return identifier; } /** - * @brief Discards the resource that corresponds to a given identifier. - * - * Handles are not invalidated and the memory used by the resource isn't - * freed as long as at least a handle keeps the resource itself alive. - * - * @param id Unique resource identifier. + * @brief Type name. + * @return Type name. */ - void discard(const resource_type id) ENTT_NOEXCEPT { - if(auto it = resources.find(id); it != resources.end()) { - resources.erase(it); - } + [[nodiscard]] constexpr std::string_view name() const ENTT_NOEXCEPT { + return alias; } private: - container_type resources; + id_type seq; + id_type identifier; + std::string_view alias; }; +/** + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects are identical, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator==(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return lhs.hash() == rhs.hash(); +} + +/** + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects differ, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator!=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than the second, false otherwise. + */ +[[nodiscard]] constexpr bool operator<(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return lhs.index() < rhs.index(); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than or equal to the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator<=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(rhs < lhs); +} +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator>(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return rhs < lhs; } +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than or equal to the second, + * false otherwise. + */ +[[nodiscard]] constexpr bool operator>=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} -#endif // ENTT_RESOURCE_CACHE_HPP +/** + * @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() ENTT_NOEXCEPT { + if constexpr(std::is_same_v>>) { + static type_info instance{std::in_place_type}; + return instance; + } else { + return type_id>>(); + } +} -// #include "resource/handle.hpp" +/*! @copydoc type_id */ +template +[[nodiscard]] const type_info &type_id(Type &&) ENTT_NOEXCEPT { + return type_id>>(); +} -// #include "resource/loader.hpp" +} // namespace entt -// #include "signal/delegate.hpp" -#ifndef ENTT_SIGNAL_DELEGATE_HPP -#define ENTT_SIGNAL_DELEGATE_HPP +#endif +// #include "../core/utility.hpp" +#ifndef ENTT_CORE_UTILITY_HPP +#define ENTT_CORE_UTILITY_HPP -#include -#include -#include -#include +#include // #include "../config/config.h" -#ifndef ENTT_CONFIG_CONFIG_H -#define ENTT_CONFIG_CONFIG_H -#ifndef ENTT_NOEXCEPT -#define ENTT_NOEXCEPT noexcept -#endif // ENTT_NOEXCEPT +namespace entt { +/*! @brief Identity function object (waiting for C++20). */ +struct identity { + /*! @brief Indicates that this is a transparent function object. */ + using is_transparent = void; -#ifndef ENTT_HS_SUFFIX -#define ENTT_HS_SUFFIX _hs -#endif // ENTT_HS_SUFFIX + /** + * @brief Returns its argument unchanged. + * @tparam Type Type of the argument. + * @param value The actual argument. + * @return The submitted value as-is. + */ + template + [[nodiscard]] constexpr Type &&operator()(Type &&value) const ENTT_NOEXCEPT { + return std::forward(value); + } +}; +/** + * @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) ENTT_NOEXCEPT { + return member; +} -#ifndef ENTT_NO_ATOMIC -#include -template -using maybe_atomic_t = std::atomic; -#else // ENTT_NO_ATOMIC -template -using maybe_atomic_t = Type; -#endif // ENTT_NO_ATOMIC +/** + * @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) ENTT_NOEXCEPT { + return func; +} +/** + * @brief Helper type for visitors. + * @tparam Func Types of function objects. + */ +template +struct overloaded: Func... { + using Func::operator()...; +}; -#ifndef ENTT_ID_TYPE -#include -#define ENTT_ID_TYPE std::uint32_t -#endif // ENTT_ID_TYPE +/** + * @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. + */ + y_combinator(Func recursive) + : func{std::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 + decltype(auto) operator()(Args &&...args) const { + return func(*this, std::forward(args)...); + } + + /*! @copydoc operator()() */ + template + decltype(auto) operator()(Args &&...args) { + return func(*this, std::forward(args)...); + } + +private: + Func func; +}; -#ifndef ENTT_PAGE_SIZE -#define ENTT_PAGE_SIZE 32768 -#endif // ENTT_PAGE_SIZE +} // namespace entt +#endif -#ifndef ENTT_DISABLE_ASSERT -#include -#define ENTT_ASSERT(condition) assert(condition) -#else // ENTT_DISABLE_ASSERT -#define ENTT_ASSERT(...) ((void)0) -#endif // ENTT_DISABLE_ASSERT +// #include "fwd.hpp" + +// #include "sigh.hpp" +#ifndef ENTT_SIGNAL_SIGH_HPP +#define ENTT_SIGNAL_SIGH_HPP + +#include +#include +#include +#include +#include +// #include "../config/config.h" +// #include "delegate.hpp" +#ifndef ENTT_SIGNAL_DELEGATE_HPP +#define ENTT_SIGNAL_DELEGATE_HPP -#endif // ENTT_CONFIG_CONFIG_H +#include +#include +#include +#include +#include +// #include "../config/config.h" +// #include "../core/type_traits.hpp" +// #include "fwd.hpp" -namespace entt { +namespace entt { /** * @cond TURN_OFF_DOXYGEN * Internal details not to be documented. */ - namespace internal { - template -auto to_function_pointer(Ret(*)(Args...)) -> Ret(*)(Args...); - +auto function_pointer(Ret (*)(Args...)) -> Ret (*)(Args...); -template -auto to_function_pointer(Ret(*)(Type *, Args...), Type *) -> Ret(*)(Args...); +template +auto function_pointer(Ret (*)(Type, Args...), Other &&) -> Ret (*)(Args...); +template +auto function_pointer(Ret (Class::*)(Args...), Other &&...) -> Ret (*)(Args...); -template -auto to_function_pointer(Ret(Class:: *)(Args...), Class *) -> Ret(*)(Args...); +template +auto function_pointer(Ret (Class::*)(Args...) const, Other &&...) -> Ret (*)(Args...); +template +auto function_pointer(Type Class::*, Other &&...) -> Type (*)(); -template -auto to_function_pointer(Ret(Class:: *)(Args...) const, Class *) -> Ret(*)(Args...); - +template +using function_pointer_t = decltype(internal::function_pointer(std::declval()...)); +template +[[nodiscard]] constexpr auto index_sequence_for(Ret (*)(Args...)) { + return std::index_sequence_for{}; } +} // namespace internal /** * Internal details not to be documented. - * @endcond TURN_OFF_DOXYGEN + * @endcond */ - /*! @brief Used to wrap a function or a member of a specified type. */ template struct connect_arg_t {}; - /*! @brief Constant of type connect_arg_t used to disambiguate calls. */ template -constexpr connect_arg_t connect_arg{}; - +inline constexpr connect_arg_t connect_arg{}; /** * @brief Basic delegate implementation. @@ -14270,105 +64168,179 @@ constexpr connect_arg_t connect_arg{}; 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 general purpose invoker with no memory overhead for - * free functions (with or without payload) and members provided along with an - * instance on which to invoke 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 proto_fn_type = Ret(const void *, Args...); + template + [[nodiscard]] auto wrap(std::index_sequence) ENTT_NOEXCEPT { + return [](const void *, Args... args) -> Ret { + [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); + return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); + }; + } + + template + [[nodiscard]] auto wrap(Type &, std::index_sequence) ENTT_NOEXCEPT { + return [](const void *payload, Args... args) -> Ret { + [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); + Type *curr = static_cast(const_cast *>(payload)); + return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); + }; + } + + template + [[nodiscard]] auto wrap(Type *, std::index_sequence) ENTT_NOEXCEPT { + return [](const void *payload, Args... args) -> Ret { + [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); + Type *curr = static_cast(const_cast *>(payload)); + return static_cast(std::invoke(Candidate, curr, std::forward>>(std::get(arguments))...)); + }; + } public: + /*! @brief Function type of the contained target. */ + using function_type = Ret(const void *, Args...); /*! @brief Function type of the delegate. */ - using function_type = Ret(Args...); + using type = Ret(Args...); + /*! @brief Return type of the delegate. */ + using result_type = Ret; /*! @brief Default constructor. */ delegate() ENTT_NOEXCEPT - : fn{nullptr}, data{nullptr} - {} + : instance{nullptr}, + fn{nullptr} {} /** - * @brief Constructs a delegate and connects a free function to it. - * @tparam Function A valid free function pointer. + * @brief Constructs a delegate and connects a free function or an unbound + * member. + * @tparam Candidate Function or member to connect to the delegate. */ - template - delegate(connect_arg_t) ENTT_NOEXCEPT - : delegate{} - { - connect(); + template + delegate(connect_arg_t) ENTT_NOEXCEPT { + connect(); } /** - * @brief Constructs a delegate and connects a member for a given instance - * or a free function with payload. - * @tparam Candidate Member or free function to connect to the delegate. + * @brief Constructs a delegate and connects a free function with payload or + * a bound member. + * @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. + * @param value_or_instance A valid object that fits the purpose. */ template - delegate(connect_arg_t, Type *value_or_instance) ENTT_NOEXCEPT - : delegate{} - { - connect(value_or_instance); + delegate(connect_arg_t, Type &&value_or_instance) ENTT_NOEXCEPT { + connect(std::forward(value_or_instance)); } /** - * @brief Connects a free function to a delegate. - * @tparam Function A valid free function pointer. + * @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. */ - template + delegate(function_type *function, const void *payload = nullptr) ENTT_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() ENTT_NOEXCEPT { - static_assert(std::is_invocable_r_v); - data = nullptr; + instance = nullptr; - fn = [](const void *, Args... args) -> Ret { - // this allows void(...) to eat return values and avoid errors - return Ret(std::invoke(Function, args...)); - }; + if constexpr(std::is_invocable_r_v) { + fn = [](const void *, Args... args) -> Ret { + return Ret(std::invoke(Candidate, std::forward(args)...)); + }; + } else if constexpr(std::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 member function for a given instance or a free function - * with payload to a delegate. + * @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.
+ * 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 Member or free function to connect to the delegate. + * @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) ENTT_NOEXCEPT { + instance = &value_or_instance; + + if constexpr(std::is_invocable_r_v) { + fn = [](const void *payload, Args... args) -> Ret { + Type *curr = static_cast(const_cast *>(payload)); + return Ret(std::invoke(Candidate, *curr, std::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) ENTT_NOEXCEPT { - static_assert(std::is_invocable_r_v); - data = value_or_instance; + instance = value_or_instance; - fn = [](const void *payload, Args... args) -> Ret { - Type *curr = nullptr; - - if constexpr(std::is_const_v) { - curr = static_cast(payload); - } else { - curr = static_cast(const_cast(payload)); - } + if constexpr(std::is_invocable_r_v) { + fn = [](const void *payload, Args... args) -> Ret { + Type *curr = static_cast(const_cast *>(payload)); + return Ret(std::invoke(Candidate, curr, std::forward(args)...)); + }; + } else { + fn = wrap(value_or_instance, internal::index_sequence_for(internal::function_pointer_t{})); + } + } - // this allows void(...) to eat return values and avoid errors - return Ret(std::invoke(Candidate, curr, args...)); - }; + /** + * @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) ENTT_NOEXCEPT { + instance = payload; + fn = function; } /** @@ -14377,16 +64349,16 @@ public: * After a reset, a delegate cannot be invoked anymore. */ void reset() ENTT_NOEXCEPT { + instance = nullptr; fn = nullptr; - data = nullptr; } /** - * @brief Returns the instance linked to a delegate, if any. - * @return An opaque pointer to the instance linked to the delegate, if any. + * @brief Returns the instance or the payload linked to a delegate, if any. + * @return An opaque pointer to the underlying data. */ - const void * instance() const ENTT_NOEXCEPT { - return data; + [[nodiscard]] const void *data() const ENTT_NOEXCEPT { + return instance; } /** @@ -14396,1664 +64368,1988 @@ public: * * @warning * Attempting to trigger an invalid delegate results in undefined - * behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * delegate has not yet been set. + * 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(fn); - return fn(data, args...); + ENTT_ASSERT(static_cast(*this), "Uninitialized delegate"); + return fn(instance, std::forward(args)...); } /** * @brief Checks whether a delegate actually stores a listener. * @return False if the delegate is empty, true otherwise. */ - explicit operator bool() const ENTT_NOEXCEPT { - // no need to test also data - return fn; + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + // no need to also test instance + return !(fn == nullptr); } /** - * @brief Checks if the connected functions differ. - * - * Instances connected to delegates are ignored by this operator. Use the - * `instance` member function instead. - * + * @brief Compares the contents of two delegates. * @param other Delegate with which to compare. - * @return False if the connected functions differ, true otherwise. + * @return False if the two contents differ, true otherwise. */ - bool operator==(const delegate &other) const ENTT_NOEXCEPT { - return fn == other.fn; + [[nodiscard]] bool operator==(const delegate &other) const ENTT_NOEXCEPT { + return fn == other.fn && instance == other.instance; } private: - proto_fn_type *fn; - const void *data; + const void *instance; + function_type *fn; }; - /** - * @brief Checks if the connected functions differ. - * - * Instances connected to delegates are ignored by this operator. Use the - * `instance` member function instead. - * + * @brief Compares the contents of two delegates. * @tparam Ret Return type of a function type. * @tparam Args Types of arguments of a function type. * @param lhs A valid delegate object. * @param rhs A valid delegate object. - * @return True if the connected functions differ, false otherwise. + * @return True if the two contents differ, false otherwise. */ template -bool operator!=(const delegate &lhs, const delegate &rhs) ENTT_NOEXCEPT { +[[nodiscard]] bool operator!=(const delegate &lhs, const delegate &rhs) ENTT_NOEXCEPT { return !(lhs == rhs); } - /** * @brief Deduction guide. - * - * It allows to deduce the function type of the delegate directly from a - * function provided to the constructor. - * - * @tparam Function A valid free function pointer. + * @tparam Candidate Function or member to connect to the delegate. */ -template -delegate(connect_arg_t) ENTT_NOEXCEPT --> delegate>; - +template +delegate(connect_arg_t) -> delegate>>; /** * @brief Deduction guide. - * - * It allows to deduce the function type of the delegate directly from a member - * or a free function with payload provided to the constructor. - * - * @tparam Candidate Member or free function to connect to the delegate. + * @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 *) ENTT_NOEXCEPT --> delegate()))>>; - - -} - - -#endif // ENTT_SIGNAL_DELEGATE_HPP - -// #include "signal/dispatcher.hpp" -#ifndef ENTT_SIGNAL_DISPATCHER_HPP -#define ENTT_SIGNAL_DISPATCHER_HPP - - -#include -#include -#include -#include -// #include "../config/config.h" - -// #include "../core/family.hpp" -#ifndef ENTT_CORE_FAMILY_HPP -#define ENTT_CORE_FAMILY_HPP +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; -#include -// #include "../config/config.h" -#ifndef ENTT_CONFIG_CONFIG_H -#define ENTT_CONFIG_CONFIG_H - +} // namespace entt -#ifndef ENTT_NOEXCEPT -#define ENTT_NOEXCEPT noexcept -#endif // ENTT_NOEXCEPT +#endif +// #include "fwd.hpp" -#ifndef ENTT_HS_SUFFIX -#define ENTT_HS_SUFFIX _hs -#endif // ENTT_HS_SUFFIX +namespace entt { -#ifndef ENTT_NO_ATOMIC -#include -template -using maybe_atomic_t = std::atomic; -#else // ENTT_NO_ATOMIC +/** + * @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 -using maybe_atomic_t = Type; -#endif // ENTT_NO_ATOMIC - - -#ifndef ENTT_ID_TYPE -#include -#define ENTT_ID_TYPE std::uint32_t -#endif // ENTT_ID_TYPE - - -#ifndef ENTT_PAGE_SIZE -#define ENTT_PAGE_SIZE 32768 -#endif // ENTT_PAGE_SIZE - - -#ifndef ENTT_DISABLE_ASSERT -#include -#define ENTT_ASSERT(condition) assert(condition) -#else // ENTT_DISABLE_ASSERT -#define ENTT_ASSERT(...) ((void)0) -#endif // ENTT_DISABLE_ASSERT - - -#endif // ENTT_CONFIG_CONFIG_H - - - -namespace entt { +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 Dynamic identifier generator. + * @brief Unmanaged signal handler. * - * Utility class template that can be used to assign unique identifiers to types - * at runtime. Use different specializations to create separate sets of - * identifiers. + * 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 family { - inline static maybe_atomic_t identifier; +template +class sigh { + /*! @brief A sink is allowed to modify a signal. */ + friend class sink>; - template - inline static const auto inner = identifier++; + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using container_type = std::vector, typename alloc_traits::template rebind_alloc>>; public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; /*! @brief Unsigned integer type. */ - using family_type = ENTT_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 in place of family_type here - inline static const family_type type = inner...>; -}; - - -} - - -#endif // ENTT_CORE_FAMILY_HPP - -// #include "../core/type_traits.hpp" -#ifndef ENTT_CORE_TYPE_TRAITS_HPP -#define ENTT_CORE_TYPE_TRAITS_HPP - - -#include -// #include "../core/hashed_string.hpp" -#ifndef ENTT_CORE_HASHED_STRING_HPP -#define ENTT_CORE_HASHED_STRING_HPP - - -#include -// #include "../config/config.h" -#ifndef ENTT_CONFIG_CONFIG_H -#define ENTT_CONFIG_CONFIG_H - - -#ifndef ENTT_NOEXCEPT -#define ENTT_NOEXCEPT noexcept -#endif // ENTT_NOEXCEPT - - -#ifndef ENTT_HS_SUFFIX -#define ENTT_HS_SUFFIX _hs -#endif // ENTT_HS_SUFFIX - - -#ifndef ENTT_NO_ATOMIC -#include -template -using maybe_atomic_t = std::atomic; -#else // ENTT_NO_ATOMIC -template -using maybe_atomic_t = Type; -#endif // ENTT_NO_ATOMIC - - -#ifndef ENTT_ID_TYPE -#include -#define ENTT_ID_TYPE std::uint32_t -#endif // ENTT_ID_TYPE - - -#ifndef ENTT_PAGE_SIZE -#define ENTT_PAGE_SIZE 32768 -#endif // ENTT_PAGE_SIZE - - -#ifndef ENTT_DISABLE_ASSERT -#include -#define ENTT_ASSERT(condition) assert(condition) -#else // ENTT_DISABLE_ASSERT -#define ENTT_ASSERT(...) ((void)0) -#endif // ENTT_DISABLE_ASSERT - - -#endif // ENTT_CONFIG_CONFIG_H - - + using size_type = std::size_t; + /*! @brief Sink type. */ + using sink_type = sink>; -namespace entt { + /*! @brief Default constructor. */ + sigh() + : sigh{allocator_type{}} {} + /** + * @brief Constructs a signal handler with a given allocator. + * @param allocator The allocator to use. + */ + explicit sigh(const allocator_type &allocator) + : calls{allocator} {} -/** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. - */ + /** + * @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} {} -namespace internal { + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + sigh(sigh &&other) ENTT_NOEXCEPT + : calls{std::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) ENTT_NOEXCEPT + : calls{std::move(other.calls), allocator} {} -template -struct fnv1a_traits; + /** + * @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) ENTT_NOEXCEPT { + calls = std::move(other.calls); + return *this; + } -template<> -struct fnv1a_traits { - static constexpr std::uint32_t offset = 2166136261; - static constexpr std::uint32_t prime = 16777619; -}; + /** + * @brief Exchanges the contents with those of a given signal handler. + * @param other Signal handler to exchange the content with. + */ + void swap(sigh &other) { + using std::swap; + swap(calls, other.calls); + } + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return calls.get_allocator(); + } -template<> -struct fnv1a_traits { - static constexpr std::uint64_t offset = 14695981039346656037ull; - static constexpr std::uint64_t prime = 1099511628211ull; -}; + /** + * @brief Instance type when it comes to connecting member functions. + * @tparam Class Type of class to which the member function belongs. + */ + template + using instance_type = Class *; + /** + * @brief Number of listeners connected to the signal. + * @return Number of listeners currently connected. + */ + [[nodiscard]] size_type size() const ENTT_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 ENTT_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 &&call: std::as_const(calls)) { + call(args...); + } + } -/** - * Internal details not to be documented. - * @endcond TURN_OFF_DOXYGEN - */ + /** + * @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 &&call: calls) { + if constexpr(std::is_void_v) { + if constexpr(std::is_invocable_r_v) { + call(args...); + if(func()) { break; } + } else { + call(args...); + func(); + } + } else { + if constexpr(std::is_invocable_r_v) { + if(func(call(args...))) { break; } + } else { + func(call(args...)); + } + } + } + } +private: + container_type calls; +}; /** - * @brief Zero overhead unique identifier. + * @brief Connection class. * - * A hashed string is a compile-time tool that allows users to use - * human-readable identifers 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. + * Opaque object the aim of which is to allow users to release an already + * estabilished connection without having to keep a reference to the signal or + * the sink that generated it. */ -class hashed_string { - using traits_type = internal::fnv1a_traits; - - struct const_wrapper { - // non-explicit constructor on purpose - constexpr const_wrapper(const char *curr) ENTT_NOEXCEPT: str{curr} {} - const char *str; - }; +class connection { + /*! @brief A sink is allowed to create connection objects. */ + template + friend class sink; - // Fowler–Noll–Vo hash function v. 1a - the good - inline static constexpr ENTT_ID_TYPE helper(ENTT_ID_TYPE partial, const char *curr) ENTT_NOEXCEPT { - return curr[0] == 0 ? partial : helper((partial^curr[0])*traits_type::prime, curr+1); - } + connection(delegate fn, void *ref) + : disconnect{fn}, signal{ref} {} public: - /*! @brief Unsigned integer type. */ - using hash_type = ENTT_ID_TYPE; + /*! @brief Default constructor. */ + connection() = default; /** - * @brief Returns directly the numeric representation of a string. - * - * Forcing template resolution avoids implicit conversions. An - * human-readable identifier can be anything but a plain, old bunch of - * characters.
- * Example of use: - * @code{.cpp} - * const auto value = hashed_string::to_value("my.png"); - * @endcode - * - * @tparam N Number of characters of the identifier. - * @param str Human-readable identifer. - * @return The numeric representation of the string. + * @brief Checks whether a connection is properly initialized. + * @return True if the connection is properly initialized, false otherwise. */ - template - inline static constexpr hash_type to_value(const char (&str)[N]) ENTT_NOEXCEPT { - return helper(traits_type::offset, str); + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return static_cast(disconnect); } - /** - * @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. - */ - inline static hash_type to_value(const_wrapper wrapper) ENTT_NOEXCEPT { - return helper(traits_type::offset, wrapper.str); + /*! @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 + * estabilished 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 Returns directly the numeric representation of a string view. - * @param str Human-readable identifer. - * @param size Length of the string to hash. - * @return The numeric representation of the string. + * @brief Constructs a scoped connection from a basic connection. + * @param other A valid connection object. */ - inline static hash_type to_value(const char *str, std::size_t size) ENTT_NOEXCEPT { - ENTT_ID_TYPE partial{traits_type::offset}; - while(size--) { partial = (partial^(str++)[0])*traits_type::prime; } - return partial; - } + scoped_connection(const connection &other) + : conn{other} {} - /*! @brief Constructs an empty hashed string. */ - constexpr hashed_string() ENTT_NOEXCEPT - : str{nullptr}, hash{} - {} + /*! @brief Default copy constructor, deleted on purpose. */ + scoped_connection(const scoped_connection &) = delete; /** - * @brief Constructs a hashed string from an array of const chars. - * - * Forcing template resolution avoids implicit conversions. An - * human-readable identifier can be anything but a plain, old bunch of - * characters.
- * Example of use: - * @code{.cpp} - * hashed_string hs{"my.png"}; - * @endcode - * - * @tparam N Number of characters of the identifier. - * @param curr Human-readable identifer. + * @brief Move constructor. + * @param other The scoped connection to move from. */ - template - constexpr hashed_string(const char (&curr)[N]) ENTT_NOEXCEPT - : str{curr}, hash{helper(traits_type::offset, curr)} - {} + scoped_connection(scoped_connection &&other) ENTT_NOEXCEPT + : conn{std::exchange(other.conn, {})} {} + + /*! @brief Automatically breaks the link on destruction. */ + ~scoped_connection() { + conn.release(); + } /** - * @brief Explicit constructor on purpose to avoid constructing a hashed - * string directly from a `const char *`. - * @param wrapper Helps achieving the purpose by relying on overloading. + * @brief Default copy assignment operator, deleted on purpose. + * @return This scoped connection. */ - explicit constexpr hashed_string(const_wrapper wrapper) ENTT_NOEXCEPT - : str{wrapper.str}, hash{helper(traits_type::offset, wrapper.str)} - {} + scoped_connection &operator=(const scoped_connection &) = delete; /** - * @brief Returns the human-readable representation of a hashed string. - * @return The string used to initialize the instance. + * @brief Move assignment operator. + * @param other The scoped connection to move from. + * @return This scoped connection. */ - constexpr const char * data() const ENTT_NOEXCEPT { - return str; + scoped_connection &operator=(scoped_connection &&other) ENTT_NOEXCEPT { + conn = std::exchange(other.conn, {}); + return *this; } /** - * @brief Returns the numeric representation of a hashed string. - * @return The numeric representation of the instance. + * @brief Acquires a connection. + * @param other The connection object to acquire. + * @return This scoped connection. */ - constexpr hash_type value() const ENTT_NOEXCEPT { - return hash; + scoped_connection &operator=(connection other) { + conn = std::move(other); + return *this; } /** - * @brief Returns the human-readable representation of a hashed string. - * @return The string used to initialize the instance. + * @brief Checks whether a scoped connection is properly initialized. + * @return True if the connection is properly initialized, false otherwise. */ - constexpr operator const char *() const ENTT_NOEXCEPT { return str; } - - /*! @copydoc value */ - constexpr operator hash_type() const ENTT_NOEXCEPT { return hash; } + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return static_cast(conn); + } - /** - * @brief Compares two hashed strings. - * @param other Hashed string with which to compare. - * @return True if the two hashed strings are identical, false otherwise. - */ - constexpr bool operator==(const hashed_string &other) const ENTT_NOEXCEPT { - return hash == other.hash; + /*! @brief Breaks the connection. */ + void release() { + conn.release(); } private: - const char *str; - hash_type hash; + connection conn; }; - -/** - * @brief Compares two hashed strings. - * @param lhs A valid hashed string. - * @param rhs A valid hashed string. - * @return True if the two hashed strings are identical, false otherwise. - */ -constexpr bool operator!=(const hashed_string &lhs, const hashed_string &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); -} - - -} - - /** - * @brief User defined literal for hashed strings. - * @param str The literal without its suffix. - * @return A properly initialized hashed string. + * @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. */ -constexpr entt::hashed_string operator"" ENTT_HS_SUFFIX(const char *str, std::size_t) ENTT_NOEXCEPT { - return entt::hashed_string{str}; -} +template +class sink> { + using signal_type = sigh; + using difference_type = typename signal_type::container_type::difference_type; + template + static void release(Type value_or_instance, void *signal) { + sink{*static_cast(signal)}.disconnect(value_or_instance); + } -#endif // ENTT_CORE_HASHED_STRING_HPP + template + static void release(void *signal) { + sink{*static_cast(signal)}.disconnect(); + } +public: + /** + * @brief Constructs a sink that is allowed to modify a given signal. + * @param ref A valid reference to a signal object. + */ + sink(sigh &ref) ENTT_NOEXCEPT + : offset{}, + 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 ENTT_NOEXCEPT { + return signal->calls.empty(); + } -namespace entt { + /** + * @brief Returns a sink that connects before a given free function or an + * unbound member. + * @tparam Function A valid free function pointer. + * @return A properly initialized sink object. + */ + template + [[nodiscard]] sink before() { + delegate call{}; + call.template connect(); + const auto &calls = signal->calls; + const auto it = std::find(calls.cbegin(), calls.cend(), std::move(call)); -/** - * @brief A class to use to push around lists of types, nothing more. - * @tparam Type Types provided by the given type list. - */ -template -struct type_list { - /*! @brief Unsigned integer type. */ - static constexpr auto size = sizeof...(Type); -}; + sink other{*this}; + other.offset = calls.cend() - it; + return other; + } + /** + * @brief Returns a sink that connects before a free function with payload + * or a bound member. + * @tparam Candidate Member or free function to look for. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid object that fits the purpose. + * @return A properly initialized sink object. + */ + template + [[nodiscard]] sink before(Type &&value_or_instance) { + delegate call{}; + call.template connect(value_or_instance); -/*! @brief Primary template isn't defined on purpose. */ -template -struct type_list_cat; + const auto &calls = signal->calls; + const auto it = std::find(calls.cbegin(), calls.cend(), std::move(call)); + sink other{*this}; + other.offset = calls.cend() - it; + return other; + } -/*! @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 Returns a sink that connects before a given instance or specific + * payload. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid object that fits the purpose. + * @return A properly initialized sink object. + */ + template + [[nodiscard]] sink before(Type &value_or_instance) { + return before(&value_or_instance); + } + /** + * @brief Returns a sink that connects before a given instance or specific + * payload. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid pointer that fits the purpose. + * @return A properly initialized sink object. + */ + template + [[nodiscard]] sink before(Type *value_or_instance) { + sink other{*this}; -/** - * @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 = typename type_list_cat, List...>::type; -}; + if(value_or_instance) { + const auto &calls = signal->calls; + const auto it = std::find_if(calls.cbegin(), calls.cend(), [value_or_instance](const auto &delegate) { + return delegate.data() == value_or_instance; + }); + other.offset = calls.cend() - it; + } -/** - * @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; -}; + return other; + } + /** + * @brief Returns a sink that connects before anything else. + * @return A properly initialized sink object. + */ + [[nodiscard]] sink before() { + sink other{*this}; + other.offset = signal->calls.size(); + return other; + } -/** - * @brief Helper type. - * @tparam List Type lists to concatenate. - */ -template -using type_list_cat_t = typename type_list_cat::type; + /** + * @brief Connects a free function or an unbound member to a signal. + * + * The signal handler performs checks to avoid multiple connections for the + * same function. + * + * @tparam Candidate Function or member to connect to the signal. + * @return A properly initialized connection object. + */ + template + connection connect() { + disconnect(); + delegate call{}; + call.template connect(); + signal->calls.insert(signal->calls.end() - offset, std::move(call)); -/*! @brief Primary template isn't defined on purpose. */ -template -struct type_list_unique; + delegate conn{}; + conn.template connect<&release>(); + return {std::move(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. On the other side, the signal handler performs + * checks to avoid multiple connections for the same function.
+ * 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 object that fits the purpose. + * @return A properly initialized connection object. + */ + template + connection connect(Type &&value_or_instance) { + disconnect(value_or_instance); -/** - * @brief Removes duplicates types from a type list. - * @tparam Type One of the types provided by the given type list. - * @tparam Other The other types provided by the given type list. - */ -template -struct type_list_unique> { - /*! @brief A type list without duplicate types. */ - using type = std::conditional_t< - std::disjunction_v...>, - typename type_list_unique>::type, - type_list_cat_t, typename type_list_unique>::type> - >; -}; + delegate call{}; + call.template connect(value_or_instance); + signal->calls.insert(signal->calls.end() - offset, std::move(call)); + delegate conn{}; + conn.template connect<&release>(value_or_instance); + return {std::move(conn), signal}; + } -/*! @brief Removes duplicates types from a type list. */ -template<> -struct type_list_unique> { - /*! @brief A type list without duplicate types. */ - using type = type_list<>; -}; + /** + * @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() { + auto &calls = signal->calls; + delegate call{}; + call.template connect(); + calls.erase(std::remove(calls.begin(), calls.end(), std::move(call)), calls.end()); + } + /** + * @brief Disconnects a free function with payload or a bound member from a + * signal. + * @tparam Candidate Function or member to disconnect from the signal. + * @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) { + auto &calls = signal->calls; + delegate call{}; + call.template connect(value_or_instance); + calls.erase(std::remove(calls.begin(), calls.end(), std::move(call)), calls.end()); + } -/** - * @brief Helper type. - * @tparam Type A type list. - */ -template -using type_list_unique_t = typename type_list_unique::type; + /** + * @brief Disconnects free functions with payload or bound members from a + * signal. + * @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 Disconnects free functions with payload or bound members from a + * signal. + * @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) { + if(value_or_instance) { + auto &calls = signal->calls; + auto predicate = [value_or_instance](const auto &delegate) { return delegate.data() == value_or_instance; }; + calls.erase(std::remove_if(calls.begin(), calls.end(), std::move(predicate)), calls.end()); + } + } -/*! @brief Traits class used mainly to push things across boundaries. */ -template -struct named_type_traits; + /*! @brief Disconnects all the listeners from a signal. */ + void disconnect() { + signal->calls.clear(); + } +private: + difference_type offset; + signal_type *signal; +}; /** - * @brief Specialization used to get rid of constness. - * @tparam Type Named type. + * @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 -struct named_type_traits - : named_type_traits -{}; +template +sink(sigh &) -> sink>; +} // namespace entt + +#endif -/** - * @brief Helper type. - * @tparam Type Potentially named type. - */ -template -using named_type_traits_t = typename named_type_traits::type; +namespace entt { /** - * @brief Provides the member constant `value` to true if a given type has a - * name. In all other cases, `value` is false. + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. */ -template> -struct is_named_type: std::false_type {}; +namespace internal { -/** - * @brief Provides the member constant `value` to true if a given type has a - * name. In all other cases, `value` is false. - * @tparam Type Potentially named type. - */ -template -struct is_named_type>>>: std::true_type {}; +struct basic_dispatcher_handler { + virtual ~basic_dispatcher_handler() = default; + virtual void publish() = 0; + virtual void disconnect(void *) = 0; + virtual void clear() ENTT_NOEXCEPT = 0; + virtual std::size_t size() const ENTT_NOEXCEPT = 0; +}; +template +class dispatcher_handler final: public basic_dispatcher_handler { + static_assert(std::is_same_v>, "Invalid event type"); -/** - * @brief Helper variable template. - * - * True if a given type has a name, false otherwise. - * - * @tparam Type Potentially named type. - */ -template -constexpr auto is_named_type_v = is_named_type::value; + using alloc_traits = std::allocator_traits; + using signal_type = sigh>; + using container_type = std::vector>; +public: + using allocator_type = Allocator; -} + dispatcher_handler(const allocator_type &allocator) + : signal{allocator}, + events{allocator} {} + void publish() override { + const auto length = events.size(); -/** - * @brief Utility macro to deal with an issue of MSVC. - * - * See _msvc-doesnt-expand-va-args-correctly_ on SO for all the details. - * - * @param args Argument to expand. - */ -#define ENTT_EXPAND(args) args + for(std::size_t pos{}; pos < length; ++pos) { + signal.publish(events[pos]); + } + events.erase(events.cbegin(), events.cbegin() + length); + } -/** - * @brief Makes an already existing type a named type. - * @param type Type to assign a name to. - */ -#define ENTT_NAMED_TYPE(type)\ - template<>\ - struct entt::named_type_traits\ - : std::integral_constant\ - {\ - static_assert(std::is_same_v, type>);\ - }; + void disconnect(void *instance) override { + bucket().disconnect(instance); + } + void clear() ENTT_NOEXCEPT override { + events.clear(); + } -/** - * @brief Defines a named type (to use for structs). - * @param clazz Name of the type to define. - * @param body Body of the type to define. - */ -#define ENTT_NAMED_STRUCT_ONLY(clazz, body)\ - struct clazz body;\ - ENTT_NAMED_TYPE(clazz) + [[nodiscard]] auto bucket() ENTT_NOEXCEPT { + using sink_type = typename sigh::sink_type; + return sink_type{signal}; + } + void trigger(Event event) { + signal.publish(event); + } -/** - * @brief Defines a named type (to use for structs). - * @param ns Namespace where to define the named type. - * @param clazz Name of the type to define. - * @param body Body of the type to define. - */ -#define ENTT_NAMED_STRUCT_WITH_NAMESPACE(ns, clazz, body)\ - namespace ns { struct clazz body; }\ - ENTT_NAMED_TYPE(ns::clazz) + template + void enqueue(Args &&...args) { + if constexpr(std::is_aggregate_v) { + events.push_back(Event{std::forward(args)...}); + } else { + events.emplace_back(std::forward(args)...); + } + } + std::size_t size() const ENTT_NOEXCEPT override { + return events.size(); + } -/*! @brief Utility function to simulate macro overloading. */ -#define ENTT_NAMED_STRUCT_OVERLOAD(_1, _2, _3, FUNC, ...) FUNC -/*! @brief Defines a named type (to use for structs). */ -#define ENTT_NAMED_STRUCT(...) ENTT_EXPAND(ENTT_NAMED_STRUCT_OVERLOAD(__VA_ARGS__, ENTT_NAMED_STRUCT_WITH_NAMESPACE, ENTT_NAMED_STRUCT_ONLY,)(__VA_ARGS__)) +private: + signal_type signal; + container_type events; +}; +} // namespace internal /** - * @brief Defines a named type (to use for classes). - * @param clazz Name of the type to define. - * @param body Body of the type to define. + * Internal details not to be documented. + * @endcond */ -#define ENTT_NAMED_CLASS_ONLY(clazz, body)\ - class clazz body;\ - ENTT_NAMED_TYPE(clazz) - /** - * @brief Defines a named type (to use for classes). - * @param ns Namespace where to define the named type. - * @param clazz Name of the type to define. - * @param body Body of the type to define. + * @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 `Event`, listeners are such that they can be invoked with an argument of + * type `Event &`, 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. */ -#define ENTT_NAMED_CLASS_WITH_NAMESPACE(ns, clazz, body)\ - namespace ns { class clazz body; }\ - ENTT_NAMED_TYPE(ns::clazz) - +template +class basic_dispatcher { + template + using handler_type = internal::dispatcher_handler; -/*! @brief Utility function to simulate macro overloading. */ -#define ENTT_NAMED_CLASS_MACRO(_1, _2, _3, FUNC, ...) FUNC -/*! @brief Defines a named type (to use for classes). */ -#define ENTT_NAMED_CLASS(...) ENTT_EXPAND(ENTT_NAMED_CLASS_MACRO(__VA_ARGS__, ENTT_NAMED_CLASS_WITH_NAMESPACE, ENTT_NAMED_CLASS_ONLY,)(__VA_ARGS__)) + using key_type = id_type; + // std::shared_ptr because of its type erased allocator which is pretty useful here + using mapped_type = std::shared_ptr; + using alloc_traits = std::allocator_traits; + using container_allocator = typename alloc_traits::template rebind_alloc>; + using container_type = dense_map, container_allocator>; -#endif // ENTT_CORE_TYPE_TRAITS_HPP + template + [[nodiscard]] handler_type &assure(const id_type id) { + auto &&ptr = pools.first()[id]; -// #include "sigh.hpp" -#ifndef ENTT_SIGNAL_SIGH_HPP -#define ENTT_SIGNAL_SIGH_HPP + if(!ptr) { + const auto &allocator = pools.second(); + ptr = std::allocate_shared>(allocator, allocator); + } + return static_cast &>(*ptr); + } -#include -#include -#include -#include -#include -// #include "../config/config.h" + template + [[nodiscard]] const handler_type *assure(const id_type id) const { + auto &container = pools.first(); -// #include "delegate.hpp" + if(const auto it = container.find(id); it != container.end()) { + return static_cast *>(it->second.get()); + } -// #include "fwd.hpp" -#ifndef ENTT_SIGNAL_FWD_HPP -#define ENTT_SIGNAL_FWD_HPP + return nullptr; + } +public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; -// #include "../config/config.h" + /*! @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 Move constructor. + * @param other The instance to move from. + */ + basic_dispatcher(basic_dispatcher &&other) ENTT_NOEXCEPT + : pools{std::move(other.pools)} {} -namespace entt { + /** + * @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) ENTT_NOEXCEPT + : pools{container_type{std::move(other.pools.first()), allocator}, allocator} {} + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This dispatcher. + */ + basic_dispatcher &operator=(basic_dispatcher &&other) ENTT_NOEXCEPT { + pools = std::move(other.pools); + return *this; + } -/*! @class delegate */ -template -class delegate; + /** + * @brief Exchanges the contents with those of a given dispatcher. + * @param other Dispatcher to exchange the content with. + */ + void swap(basic_dispatcher &other) { + using std::swap; + swap(pools, other.pools); + } -/*! @class sink */ -template -class sink; + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return pools.second(); + } -/*! @class sigh */ -template -struct sigh; + /** + * @brief Returns the number of pending events for a given type. + * @tparam Event 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 + size_type size(const id_type id = type_hash::value()) const ENTT_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. + */ + size_type size() const ENTT_NOEXCEPT { + size_type count{}; -} + for(auto &&cpool: pools.first()) { + count += cpool.second->size(); + } + return count; + } -#endif // ENTT_SIGNAL_FWD_HPP + /** + * @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(Event &); + * @endcode + * + * The order of invocation of the listeners isn't guaranteed. + * + * @sa sink + * + * @tparam Event 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 Event Type of event to trigger. + * @param event An instance of the given type of event. + */ + template + void trigger(Event &&event = {}) { + trigger(type_hash>::value(), std::forward(event)); + } + /** + * @brief Triggers an immediate event on a queue of a given type. + * @tparam Event Type of event to trigger. + * @param event 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, Event &&event = {}) { + assure>(id).trigger(std::forward(event)); + } -namespace entt { + /** + * @brief Enqueues an event of the given type. + * @tparam Event 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(), std::forward(args)...); + } + /** + * @brief Enqueues an event of the given type. + * @tparam Event Type of event to enqueue. + * @param event An instance of the given type of event. + */ + template + void enqueue(Event &&event) { + enqueue_hint(type_hash>::value(), std::forward(event)); + } -/** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. - */ + /** + * @brief Enqueues an event of the given type. + * @tparam Event 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(std::forward(args)...); + } + /** + * @brief Enqueues an event of the given type. + * @tparam Event Type of event to enqueue. + * @param id Name used to map the event queue within the dispatcher. + * @param event An instance of the given type of event. + */ + template + void enqueue_hint(const id_type id, Event &&event) { + assure>(id).enqueue(std::forward(event)); + } -namespace internal { + /** + * @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); + } + } -template -struct invoker; + /** + * @brief Discards all the events stored so far in a given queue. + * @tparam Event 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() ENTT_NOEXCEPT { + for(auto &&cpool: pools.first()) { + cpool.second->clear(); + } + } -template -struct invoker { - virtual ~invoker() = default; + /** + * @brief Delivers all the pending events of a given queue. + * @tparam Event 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(); + } - bool invoke(Collector &collector, const delegate &delegate, Args... args) const { - return collector(delegate(args...)); + /*! @brief Delivers all the pending events. */ + void update() const { + for(auto &&cpool: pools.first()) { + cpool.second->publish(); + } } + +private: + compressed_pair pools; }; +} // namespace entt -template -struct invoker { - virtual ~invoker() = default; +#endif - bool invoke(Collector &, const delegate &delegate, Args... args) const { - return (delegate(args...), true); - } -}; +// #include "signal/emitter.hpp" +#ifndef ENTT_SIGNAL_EMITTER_HPP +#define ENTT_SIGNAL_EMITTER_HPP +#include +#include +#include +#include +#include +#include +#include +// #include "../config/config.h" -template -struct null_collector { - using result_type = Ret; - bool operator()(result_type) const ENTT_NOEXCEPT { return true; } -}; +// #include "../container/dense_map.hpp" +// #include "../core/fwd.hpp" -template<> -struct null_collector { - using result_type = void; - bool operator()() const ENTT_NOEXCEPT { return true; } -}; +// #include "../core/type_info.hpp" +// #include "../core/utility.hpp" + +// #include "fwd.hpp" + + +namespace entt { + +/** + * @brief General purpose event emitter. + * + * The emitter class template follows the CRTP idiom. To create a custom emitter + * type, derived classes must inherit directly from the base class as: + * + * @code{.cpp} + * struct my_emitter: emitter { + * // ... + * } + * @endcode + * + * Pools for the type of events are created internally on the fly. It's not + * required to specify in advance the full list of accepted types.
+ * Moreover, whenever an event is published, an emitter provides the listeners + * with a reference to itself along with a reference to the event. Therefore + * listeners have an handy way to work with it without incurring in the need of + * capturing a reference to the emitter. + * + * @tparam Derived Actual type of emitter that extends the class template. + */ +template +class emitter { + struct basic_pool { + virtual ~basic_pool() = default; + virtual bool empty() const ENTT_NOEXCEPT = 0; + virtual void clear() ENTT_NOEXCEPT = 0; + }; -template -struct default_collector; + template + struct pool_handler final: basic_pool { + static_assert(std::is_same_v>, "Invalid event type"); + using listener_type = std::function; + using element_type = std::pair; + using container_type = std::list; + using connection_type = typename container_type::iterator; -template -struct default_collector { - using collector_type = null_collector; -}; + [[nodiscard]] bool empty() const ENTT_NOEXCEPT override { + auto pred = [](auto &&element) { return element.first; }; + return std::all_of(once_list.cbegin(), once_list.cend(), pred) + && std::all_of(on_list.cbegin(), on_list.cend(), pred); + } -template -using default_collector_type = typename default_collector::collector_type; + void clear() ENTT_NOEXCEPT override { + if(publishing) { + for(auto &&element: once_list) { + element.first = true; + } + for(auto &&element: on_list) { + element.first = true; + } + } else { + once_list.clear(); + on_list.clear(); + } + } -} + connection_type once(listener_type listener) { + return once_list.emplace(once_list.cend(), false, std::move(listener)); + } + connection_type on(listener_type listener) { + return on_list.emplace(on_list.cend(), false, std::move(listener)); + } -/** - * Internal details not to be documented. - * @endcond TURN_OFF_DOXYGEN - */ + void erase(connection_type conn) { + conn->first = true; + if(!publishing) { + auto pred = [](auto &&element) { return element.first; }; + once_list.remove_if(pred); + on_list.remove_if(pred); + } + } -/** - * @brief Sink implementation. - * - * Primary template isn't defined on purpose. All the specializations give a - * compile-time error unless the template parameter is a function type. - * - * @tparam Function A valid function type. - */ -template -class sink; + void publish(Event &event, Derived &ref) { + container_type swap_list; + once_list.swap(swap_list); + publishing = true; -/** - * @brief Unmanaged signal handler declaration. - * - * Primary template isn't defined on purpose. All the specializations give a - * compile-time error unless the template parameter is a function type. - * - * @tparam Function A valid function type. - * @tparam Collector Type of collector to use, if any. - */ -template> -struct sigh; + for(auto &&element: on_list) { + element.first ? void() : element.second(event, ref); + } + for(auto &&element: swap_list) { + element.first ? void() : element.second(event, ref); + } -/** - * @brief Sink implementation. - * - * A sink is an opaque object used to connect listeners to signals.
- * 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 a class. - * - * @tparam Ret Return type of a function type. - * @tparam Args Types of arguments of a function type. - */ -template -class sink { - /*! @brief A signal is allowed to create sinks. */ - template - friend struct sigh; + publishing = false; - template - Type * payload_type(Ret(*)(Type *, Args...)); + on_list.remove_if([](auto &&element) { return element.first; }); + } - sink(std::vector> *ref) ENTT_NOEXCEPT - : calls{ref} - {} + private: + bool publishing{false}; + container_type once_list{}; + container_type on_list{}; + }; -public: - /** - * @brief Returns false if at least a listener is connected to the sink. - * @return True if the sink has no listeners connected, false otherwise. - */ - bool empty() const ENTT_NOEXCEPT { - return calls->empty(); + template + [[nodiscard]] pool_handler *assure() { + if(auto &&ptr = pools[type_hash::value()]; !ptr) { + auto *cpool = new pool_handler{}; + ptr.reset(cpool); + return cpool; + } else { + return static_cast *>(ptr.get()); + } } - /** - * @brief Connects a free function to a signal. - * - * The signal handler performs checks to avoid multiple connections for free - * functions. - * - * @tparam Function A valid free function pointer. - */ - template - void connect() { - disconnect(); - delegate delegate{}; - delegate.template connect(); - calls->emplace_back(std::move(delegate)); + template + [[nodiscard]] const pool_handler *assure() const { + const auto it = pools.find(type_hash::value()); + return (it == pools.cend()) ? nullptr : static_cast *>(it->second.get()); } +public: + /** @brief Type of listeners accepted for the given event. */ + template + using listener = typename pool_handler::listener_type; + /** - * @brief Connects a member function or a free function with payload to a - * signal. + * @brief Generic connection type for events. * - * 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 delegate. On the other side, the signal handler performs - * checks to avoid multiple connections for the same function.
- * 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. + * Type of the connection object returned by the event emitter whenever a + * listener for the given type is registered.
+ * It can be used to break connections still in use. * - * @tparam Candidate Member or free function 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. + * @tparam Event Type of event for which the connection is created. */ - template - void connect(Type *value_or_instance) { - if constexpr(std::is_member_function_pointer_v) { - disconnect(value_or_instance); - } else { - disconnect(); - } + template + struct connection: private pool_handler::connection_type { + /** @brief Event emitters are friend classes of connections. */ + friend class emitter; - delegate delegate{}; - delegate.template connect(value_or_instance); - calls->emplace_back(std::move(delegate)); - } + /*! @brief Default constructor. */ + connection() ENTT_NOEXCEPT = default; - /** - * @brief Disconnects a free function from a signal. - * @tparam Function A valid free function pointer. - */ - template - void disconnect() { - delegate delegate{}; + /** + * @brief Creates a connection that wraps its underlying instance. + * @param conn A connection object to wrap. + */ + connection(typename pool_handler::connection_type conn) + : pool_handler::connection_type{std::move(conn)} {} + }; - if constexpr(std::is_invocable_r_v) { - delegate.template connect(); - } else { - decltype(payload_type(Function)) payload = nullptr; - delegate.template connect(payload); - } + /*! @brief Default constructor. */ + emitter() = default; - calls->erase(std::remove(calls->begin(), calls->end(), std::move(delegate)), calls->end()); + /*! @brief Default destructor. */ + virtual ~emitter() ENTT_NOEXCEPT { + static_assert(std::is_base_of_v, Derived>, "Incorrect use of the class template"); } - /** - * @brief Disconnects a given member function from a signal. - * @tparam Member Member function to disconnect from the signal. - * @tparam Class Type of class to which the member function belongs. - * @param instance A valid instance of type pointer to `Class`. - */ - template - void disconnect(Class *instance) { - static_assert(std::is_member_function_pointer_v); - delegate delegate{}; - delegate.template connect(instance); - calls->erase(std::remove_if(calls->begin(), calls->end(), [&delegate](const auto &other) { - return other == delegate && other.instance() == delegate.instance(); - }), calls->end()); - } + /*! @brief Default move constructor. */ + emitter(emitter &&) = default; + + /*! @brief Default move assignment operator. @return This emitter. */ + emitter &operator=(emitter &&) = default; /** - * @brief Disconnects all the listeners from a signal. + * @brief Emits the given event. + * + * All the listeners registered for the specific event type are invoked with + * the given event. The event type must either have a proper constructor for + * the arguments provided or be an aggregate type. + * + * @tparam Event Type of event to publish. + * @tparam Args Types of arguments to use to construct the event. + * @param args Parameters to use to initialize the event. */ - void disconnect() { - calls->clear(); + template + void publish(Args &&...args) { + Event instance{std::forward(args)...}; + assure()->publish(instance, *static_cast(this)); } -private: - std::vector> *calls; -}; - - -/** - * @brief Unmanaged signal handler definition. - * - * Unmanaged signal handler. It works directly with naked pointers 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 used later to notify a bunch of listeners. - * * Collecting results from a set of functions like in a voting system. - * - * The default collector does nothing. To properly collect data, define and use - * a class that has a call operator the signature of which is `bool(Param)` and: - * - * * `Param` is a type to which `Ret` can be converted. - * * The return type is true if the handler must stop collecting data, false - * otherwise. - * - * @tparam Ret Return type of a function type. - * @tparam Args Types of arguments of a function type. - * @tparam Collector Type of collector to use, if any. - */ -template -struct sigh: private internal::invoker { - /*! @brief Unsigned integer type. */ - using size_type = typename std::vector>::size_type; - /*! @brief Collector type. */ - using collector_type = Collector; - /*! @brief Sink type. */ - using sink_type = entt::sink; - /** - * @brief Instance type when it comes to connecting member functions. - * @tparam Class Type of class to which the member function belongs. + * @brief Registers a long-lived listener with the event emitter. + * + * This method can be used to register a listener designed to be invoked + * more than once for the given event type.
+ * The connection returned by the method can be freely discarded. It's meant + * to be used later to disconnect the listener if required. + * + * The listener is as a callable object that can be moved and the type of + * which is _compatible_ with `void(Event &, Derived &)`. + * + * @note + * Whenever an event is emitted, the emitter provides the listener with a + * reference to the derived class. Listeners don't have to capture those + * instances for later uses. + * + * @tparam Event Type of event to which to connect the listener. + * @param instance The listener to register. + * @return Connection object that can be used to disconnect the listener. */ - template - using instance_type = Class *; + template + connection on(listener instance) { + return assure()->on(std::move(instance)); + } /** - * @brief Number of listeners connected to the signal. - * @return Number of listeners currently connected. + * @brief Registers a short-lived listener with the event emitter. + * + * This method can be used to register a listener designed to be invoked + * only once for the given event type.
+ * The connection returned by the method can be freely discarded. It's meant + * to be used later to disconnect the listener if required. + * + * The listener is as a callable object that can be moved and the type of + * which is _compatible_ with `void(Event &, Derived &)`. + * + * @note + * Whenever an event is emitted, the emitter provides the listener with a + * reference to the derived class. Listeners don't have to capture those + * instances for later uses. + * + * @tparam Event Type of event to which to connect the listener. + * @param instance The listener to register. + * @return Connection object that can be used to disconnect the listener. */ - size_type size() const ENTT_NOEXCEPT { - return calls.size(); + template + connection once(listener instance) { + return assure()->once(std::move(instance)); } /** - * @brief Returns false if at least a listener is connected to the signal. - * @return True if the signal has no listeners connected, false otherwise. + * @brief Disconnects a listener from the event emitter. + * + * Do not use twice the same connection to disconnect a listener, it results + * in undefined behavior. Once used, discard the connection object. + * + * @tparam Event Type of event of the connection. + * @param conn A valid connection. */ - bool empty() const ENTT_NOEXCEPT { - return calls.empty(); + template + void erase(connection conn) { + assure()->erase(std::move(conn)); } /** - * @brief Returns a sink object for the given signal. + * @brief Disconnects all the listeners for the given event type. * - * A sink is an opaque object used to connect listeners to signals.
- * The function type for a listener is the one of the signal to which it - * belongs. The order of invocation of the listeners isn't guaranteed. + * All the connections previously returned for the given event are + * invalidated. Using them results in undefined behavior. * - * @return A temporary sink object. + * @tparam Event Type of event to reset. */ - sink_type sink() ENTT_NOEXCEPT { - return { &calls }; + template + void clear() { + assure()->clear(); } /** - * @brief Triggers a signal. - * - * All the listeners are notified. Order isn't guaranteed. + * @brief Disconnects all the listeners. * - * @param args Arguments to use to invoke listeners. + * All the connections previously returned are invalidated. Using them + * results in undefined behavior. */ - void publish(Args... args) const { - for(auto pos = calls.size(); pos; --pos) { - auto &call = calls[pos-1]; - call(args...); + void clear() ENTT_NOEXCEPT { + for(auto &&cpool: pools) { + cpool.second->clear(); } } /** - * @brief Collects return values from the listeners. - * @param args Arguments to use to invoke listeners. - * @return An instance of the collector filled with collected data. + * @brief Checks if there are listeners registered for the specific event. + * @tparam Event Type of event to test. + * @return True if there are no listeners registered, false otherwise. */ - collector_type collect(Args... args) const { - collector_type collector; - - for(auto &&call: calls) { - if(!this->invoke(collector, call, args...)) { - break; - } - } - - return collector; + template + [[nodiscard]] bool empty() const { + const auto *cpool = assure(); + return !cpool || cpool->empty(); } /** - * @brief Swaps listeners between the two signals. - * @param lhs A valid signal object. - * @param rhs A valid signal object. + * @brief Checks if there are listeners registered with the event emitter. + * @return True if there are no listeners registered, false otherwise. */ - friend void swap(sigh &lhs, sigh &rhs) { - using std::swap; - swap(lhs.calls, rhs.calls); + [[nodiscard]] bool empty() const ENTT_NOEXCEPT { + return std::all_of(pools.cbegin(), pools.cend(), [](auto &&cpool) { + return cpool.second->empty(); + }); } private: - std::vector> calls; + dense_map, identity> pools{}; }; +} // namespace entt -} +#endif + +// #include "signal/sigh.hpp" +#ifndef ENTT_SIGNAL_SIGH_HPP +#define ENTT_SIGNAL_SIGH_HPP +#include +#include +#include +#include +#include +// #include "../config/config.h" -#endif // ENTT_SIGNAL_SIGH_HPP +// #include "delegate.hpp" +// #include "fwd.hpp" namespace entt { - /** - * @brief Basic dispatcher implementation. + * @brief Sink class. * - * 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 `Event`, listeners are such that they can be invoked with an argument of - * type `const Event &`, no matter what the return type is. + * Primary template isn't defined on purpose. All the specializations give a + * compile-time error unless the template parameter is a function type. * - * The type of the instances is `Class *` (a naked pointer). It means that users - * must guarantee that the lifetimes of the instances overcome the one of the - * dispatcher itself to avoid crashes. + * @tparam Type A valid signal handler type. */ -class dispatcher { - using event_family = family; +template +class sink; - template - using instance_type = typename sigh::template instance_type; +/** + * @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; - struct base_wrapper { - virtual ~base_wrapper() = default; - virtual void publish() = 0; - }; +/** + * @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 { + /*! @brief A sink is allowed to modify a signal. */ + friend class sink>; - template - struct signal_wrapper: base_wrapper { - using signal_type = sigh; - using sink_type = typename signal_type::sink_type; + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using container_type = std::vector, typename alloc_traits::template rebind_alloc>>; - void publish() override { - for(const auto &event: events[current]) { - signal.publish(event); - } +public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Sink type. */ + using sink_type = sink>; - events[current++].clear(); - current %= std::extent::value; - } + /*! @brief Default constructor. */ + sigh() + : sigh{allocator_type{}} {} - inline sink_type sink() ENTT_NOEXCEPT { - return signal.sink(); - } + /** + * @brief Constructs a signal handler with a given allocator. + * @param allocator The allocator to use. + */ + explicit sigh(const allocator_type &allocator) + : calls{allocator} {} - template - inline void trigger(Args &&... args) { - signal.publish({ std::forward(args)... }); - } + /** + * @brief Copy constructor. + * @param other The instance to copy from. + */ + sigh(const sigh &other) + : calls{other.calls} {} - template - inline void enqueue(Args &&... args) { - events[current].emplace_back(std::forward(args)...); - } + /** + * @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} {} - private: - signal_type signal{}; - std::vector events[2]; - int current{}; - }; + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + sigh(sigh &&other) ENTT_NOEXCEPT + : calls{std::move(other.calls)} {} - struct wrapper_data { - std::unique_ptr wrapper; - ENTT_ID_TYPE runtime_type; - }; + /** + * @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) ENTT_NOEXCEPT + : calls{std::move(other.calls), allocator} {} - template - static auto type() ENTT_NOEXCEPT { - if constexpr(is_named_type_v) { - return named_type_traits::value; - } else { - return event_family::type; - } + /** + * @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; } - template - signal_wrapper & assure() { - const auto wtype = type(); - wrapper_data *wdata = nullptr; - - if constexpr(is_named_type_v) { - const auto it = std::find_if(wrappers.begin(), wrappers.end(), [wtype](const auto &candidate) { - return candidate.wrapper && candidate.runtime_type == wtype; - }); - - wdata = (it == wrappers.cend() ? &wrappers.emplace_back() : &(*it)); - } else { - if(!(wtype < wrappers.size())) { - wrappers.resize(wtype+1); - } - - wdata = &wrappers[wtype]; - - if(wdata->wrapper && wdata->runtime_type != wtype) { - wrappers.emplace_back(); - std::swap(wrappers[wtype], wrappers.back()); - wdata = &wrappers[wtype]; - } - } - - if(!wdata->wrapper) { - wdata->wrapper = std::make_unique>(); - wdata->runtime_type = wtype; - } - - return static_cast &>(*wdata->wrapper); + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This signal handler. + */ + sigh &operator=(sigh &&other) ENTT_NOEXCEPT { + calls = std::move(other.calls); + return *this; } -public: - /*! @brief Type of sink for the given event. */ - template - using sink_type = typename signal_wrapper::sink_type; - /** - * @brief Returns a sink object for the given event. - * - * A sink is an opaque object used to connect listeners to events. - * - * The function type for a listener is: - * @code{.cpp} - * void(const Event &); - * @endcode - * - * The order of invocation of the listeners isn't guaranteed. - * - * @sa sink - * - * @tparam Event Type of event of which to get the sink. - * @return A temporary sink object. + * @brief Exchanges the contents with those of a given signal handler. + * @param other Signal handler to exchange the content with. */ - template - inline sink_type sink() ENTT_NOEXCEPT { - return assure().sink(); + void swap(sigh &other) { + using std::swap; + swap(calls, other.calls); } /** - * @brief Triggers an immediate event of the given type. - * - * All the listeners registered for the given type are immediately notified. - * The event is discarded after the execution. - * - * @tparam Event Type of event to trigger. - * @tparam Args Types of arguments to use to construct the event. - * @param args Arguments to use to construct the event. + * @brief Returns the associated allocator. + * @return The associated allocator. */ - template - inline void trigger(Args &&... args) { - assure().trigger(std::forward(args)...); + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return calls.get_allocator(); } /** - * @brief Triggers an immediate event of the given type. - * - * All the listeners registered for the given type are immediately notified. - * The event is discarded after the execution. - * - * @tparam Event Type of event to trigger. - * @param event An instance of the given type of event. + * @brief Instance type when it comes to connecting member functions. + * @tparam Class Type of class to which the member function belongs. */ - template - inline void trigger(Event &&event) { - assure>().trigger(std::forward(event)); - } + template + using instance_type = Class *; /** - * @brief Enqueues an event of the given type. - * - * An event of the given type is queued. No listener is invoked. Use the - * `update` member function to notify listeners when ready. - * - * @tparam Event 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. + * @brief Number of listeners connected to the signal. + * @return Number of listeners currently connected. */ - template - inline void enqueue(Args &&... args) { - assure().enqueue(std::forward(args)...); + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return calls.size(); } /** - * @brief Enqueues an event of the given type. - * - * An event of the given type is queued. No listener is invoked. Use the - * `update` member function to notify listeners when ready. - * - * @tparam Event Type of event to enqueue. - * @param event An instance of the given type of event. + * @brief Returns false if at least a listener is connected to the signal. + * @return True if the signal has no listeners connected, false otherwise. */ - template - inline void enqueue(Event &&event) { - assure>().enqueue(std::forward(event)); + [[nodiscard]] bool empty() const ENTT_NOEXCEPT { + return calls.empty(); } /** - * @brief Delivers all the pending events of the given type. + * @brief Triggers a signal. * - * This method is blocking and it doesn't return until all the events are - * delivered to the registered listeners. It's responsibility of the users - * to reduce at a minimum the time spent in the bodies of the listeners. + * All the listeners are notified. Order isn't guaranteed. * - * @tparam Event Type of events to send. + * @param args Arguments to use to invoke listeners. */ - template - inline void update() { - assure().publish(); + void publish(Args... args) const { + for(auto &&call: std::as_const(calls)) { + call(args...); + } } /** - * @brief Delivers all the pending events. + * @brief Collects return values from the listeners. * - * This method is blocking and it doesn't return until all the events are - * delivered to the registered listeners. It's responsibility of the users - * to reduce at a minimum the time spent in the bodies of 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. */ - inline void update() const { - for(auto pos = wrappers.size(); pos; --pos) { - auto &wdata = wrappers[pos-1]; - - if(wdata.wrapper) { - wdata.wrapper->publish(); + template + void collect(Func func, Args... args) const { + for(auto &&call: calls) { + if constexpr(std::is_void_v) { + if constexpr(std::is_invocable_r_v) { + call(args...); + if(func()) { break; } + } else { + call(args...); + func(); + } + } else { + if constexpr(std::is_invocable_r_v) { + if(func(call(args...))) { break; } + } else { + func(call(args...)); + } } } } private: - std::vector wrappers; + container_type calls; }; - -} - - -#endif // ENTT_SIGNAL_DISPATCHER_HPP - -// #include "signal/emitter.hpp" -#ifndef ENTT_SIGNAL_EMITTER_HPP -#define ENTT_SIGNAL_EMITTER_HPP - - -#include -#include -#include -#include -#include -#include -#include -// #include "../config/config.h" - -// #include "../core/family.hpp" - -// #include "../core/type_traits.hpp" - - - -namespace entt { - - /** - * @brief General purpose event emitter. - * - * The emitter class template follows the CRTP idiom. To create a custom emitter - * type, derived classes must inherit directly from the base class as: - * - * @code{.cpp} - * struct my_emitter: emitter { - * // ... - * } - * @endcode - * - * Handlers for the type of events are created internally on the fly. It's not - * required to specify in advance the full list of accepted types.
- * Moreover, whenever an event is published, an emitter provides the listeners - * with a reference to itself along with a const reference to the event. - * Therefore listeners have an handy way to work with it without incurring in - * the need of capturing a reference to the emitter. + * @brief Connection class. * - * @tparam Derived Actual type of emitter that extends the class template. + * Opaque object the aim of which is to allow users to release an already + * estabilished connection without having to keep a reference to the signal or + * the sink that generated it. */ -template -class emitter { - using handler_family = family; - - struct base_handler { - virtual ~base_handler() = default; - virtual bool empty() const ENTT_NOEXCEPT = 0; - virtual void clear() ENTT_NOEXCEPT = 0; - }; - - template - struct event_handler: base_handler { - using listener_type = std::function; - using element_type = std::pair; - using container_type = std::list; - using connection_type = typename container_type::iterator; - - bool empty() const ENTT_NOEXCEPT override { - auto pred = [](auto &&element) { return element.first; }; - - return std::all_of(once_list.cbegin(), once_list.cend(), pred) && - std::all_of(on_list.cbegin(), on_list.cend(), pred); - } - - void clear() ENTT_NOEXCEPT override { - if(publishing) { - auto func = [](auto &&element) { element.first = true; }; - std::for_each(once_list.begin(), once_list.end(), func); - std::for_each(on_list.begin(), on_list.end(), func); - } else { - once_list.clear(); - on_list.clear(); - } - } +class connection { + /*! @brief A sink is allowed to create connection objects. */ + template + friend class sink; - inline connection_type once(listener_type listener) { - return once_list.emplace(once_list.cend(), false, std::move(listener)); - } + connection(delegate fn, void *ref) + : disconnect{fn}, signal{ref} {} - inline connection_type on(listener_type listener) { - return on_list.emplace(on_list.cend(), false, std::move(listener)); - } +public: + /*! @brief Default constructor. */ + connection() = default; - void erase(connection_type conn) ENTT_NOEXCEPT { - conn->first = true; + /** + * @brief Checks whether a connection is properly initialized. + * @return True if the connection is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return static_cast(disconnect); + } - if(!publishing) { - auto pred = [](auto &&element) { return element.first; }; - once_list.remove_if(pred); - on_list.remove_if(pred); - } + /*! @brief Breaks the connection. */ + void release() { + if(disconnect) { + disconnect(signal); + disconnect.reset(); } + } - void publish(const Event &event, Derived &ref) { - container_type swap_list; - once_list.swap(swap_list); - - auto func = [&event, &ref](auto &&element) { - return element.first ? void() : element.second(event, ref); - }; +private: + delegate disconnect; + void *signal{}; +}; - publishing = true; +/** + * @brief Scoped connection class. + * + * Opaque object the aim of which is to allow users to release an already + * estabilished 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; - std::for_each(on_list.rbegin(), on_list.rend(), func); - std::for_each(swap_list.rbegin(), swap_list.rend(), func); + /** + * @brief Constructs a scoped connection from a basic connection. + * @param other A valid connection object. + */ + scoped_connection(const connection &other) + : conn{other} {} - publishing = false; + /*! @brief Default copy constructor, deleted on purpose. */ + scoped_connection(const scoped_connection &) = delete; - on_list.remove_if([](auto &&element) { return element.first; }); - } + /** + * @brief Move constructor. + * @param other The scoped connection to move from. + */ + scoped_connection(scoped_connection &&other) ENTT_NOEXCEPT + : conn{std::exchange(other.conn, {})} {} - private: - bool publishing{false}; - container_type once_list{}; - container_type on_list{}; - }; + /*! @brief Automatically breaks the link on destruction. */ + ~scoped_connection() { + conn.release(); + } - struct handler_data { - std::unique_ptr handler; - ENTT_ID_TYPE runtime_type; - }; + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This scoped connection. + */ + scoped_connection &operator=(const scoped_connection &) = delete; - template - static auto type() ENTT_NOEXCEPT { - if constexpr(is_named_type_v) { - return named_type_traits::value; - } else { - return handler_family::type; - } + /** + * @brief Move assignment operator. + * @param other The scoped connection to move from. + * @return This scoped connection. + */ + scoped_connection &operator=(scoped_connection &&other) ENTT_NOEXCEPT { + conn = std::exchange(other.conn, {}); + return *this; } - template - event_handler * assure() const ENTT_NOEXCEPT { - const auto htype = type(); - handler_data *hdata = nullptr; + /** + * @brief Acquires a connection. + * @param other The connection object to acquire. + * @return This scoped connection. + */ + scoped_connection &operator=(connection other) { + conn = std::move(other); + return *this; + } - if constexpr(is_named_type_v) { - const auto it = std::find_if(handlers.begin(), handlers.end(), [htype](const auto &candidate) { - return candidate.handler && candidate.runtime_type == htype; - }); + /** + * @brief Checks whether a scoped connection is properly initialized. + * @return True if the connection is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return static_cast(conn); + } - hdata = (it == handlers.cend() ? &handlers.emplace_back() : &(*it)); - } else { - if(!(htype < handlers.size())) { - handlers.resize(htype+1); - } + /*! @brief Breaks the connection. */ + void release() { + conn.release(); + } - hdata = &handlers[htype]; +private: + connection conn; +}; - if(hdata->handler && hdata->runtime_type != htype) { - handlers.emplace_back(); - std::swap(handlers[htype], handlers.back()); - hdata = &handlers[htype]; - } - } +/** + * @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 difference_type = typename signal_type::container_type::difference_type; - if(!hdata->handler) { - hdata->handler = std::make_unique>(); - hdata->runtime_type = htype; - } + template + static void release(Type value_or_instance, void *signal) { + sink{*static_cast(signal)}.disconnect(value_or_instance); + } - return static_cast *>(hdata->handler.get()); + template + static void release(void *signal) { + sink{*static_cast(signal)}.disconnect(); } public: - /** @brief Type of listeners accepted for the given event. */ - template - using listener = typename event_handler::listener_type; - /** - * @brief Generic connection type for events. - * - * Type of the connection object returned by the event emitter whenever a - * listener for the given type is registered.
- * It can be used to break connections still in use. - * - * @tparam Event Type of event for which the connection is created. + * @brief Constructs a sink that is allowed to modify a given signal. + * @param ref A valid reference to a signal object. */ - template - struct connection: private event_handler::connection_type { - /** @brief Event emitters are friend classes of connections. */ - friend class emitter; + sink(sigh &ref) ENTT_NOEXCEPT + : offset{}, + signal{&ref} {} - /*! @brief Default constructor. */ - connection() ENTT_NOEXCEPT = default; + /** + * @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 ENTT_NOEXCEPT { + return signal->calls.empty(); + } - /** - * @brief Creates a connection that wraps its underlying instance. - * @param conn A connection object to wrap. - */ - connection(typename event_handler::connection_type conn) - : event_handler::connection_type{std::move(conn)} - {} - }; + /** + * @brief Returns a sink that connects before a given free function or an + * unbound member. + * @tparam Function A valid free function pointer. + * @return A properly initialized sink object. + */ + template + [[nodiscard]] sink before() { + delegate call{}; + call.template connect(); - /*! @brief Default constructor. */ - emitter() ENTT_NOEXCEPT = default; + const auto &calls = signal->calls; + const auto it = std::find(calls.cbegin(), calls.cend(), std::move(call)); - /*! @brief Default destructor. */ - virtual ~emitter() ENTT_NOEXCEPT { - static_assert(std::is_base_of_v, Derived>); + sink other{*this}; + other.offset = calls.cend() - it; + return other; } - /*! @brief Default move constructor. */ - emitter(emitter &&) = default; + /** + * @brief Returns a sink that connects before a free function with payload + * or a bound member. + * @tparam Candidate Member or free function to look for. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid object that fits the purpose. + * @return A properly initialized sink object. + */ + template + [[nodiscard]] sink before(Type &&value_or_instance) { + delegate call{}; + call.template connect(value_or_instance); - /*! @brief Default move assignment operator. @return This emitter. */ - emitter & operator=(emitter &&) = default; + const auto &calls = signal->calls; + const auto it = std::find(calls.cbegin(), calls.cend(), std::move(call)); + + sink other{*this}; + other.offset = calls.cend() - it; + return other; + } /** - * @brief Emits the given event. - * - * All the listeners registered for the specific event type are invoked with - * the given event. The event type must either have a proper constructor for - * the arguments provided or be an aggregate type. - * - * @tparam Event Type of event to publish. - * @tparam Args Types of arguments to use to construct the event. - * @param args Parameters to use to initialize the event. + * @brief Returns a sink that connects before a given instance or specific + * payload. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid object that fits the purpose. + * @return A properly initialized sink object. */ - template - void publish(Args &&... args) { - assure()->publish({ std::forward(args)... }, *static_cast(this)); + template + [[nodiscard]] sink before(Type &value_or_instance) { + return before(&value_or_instance); } /** - * @brief Registers a long-lived listener with the event emitter. - * - * This method can be used to register a listener designed to be invoked - * more than once for the given event type.
- * The connection returned by the method can be freely discarded. It's meant - * to be used later to disconnect the listener if required. - * - * The listener is as a callable object that can be moved and the type of - * which is `void(const Event &, Derived &)`. - * - * @note - * Whenever an event is emitted, the emitter provides the listener with a - * reference to the derived class. Listeners don't have to capture those - * instances for later uses. - * - * @tparam Event Type of event to which to connect the listener. - * @param instance The listener to register. - * @return Connection object that can be used to disconnect the listener. + * @brief Returns a sink that connects before a given instance or specific + * payload. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid pointer that fits the purpose. + * @return A properly initialized sink object. */ - template - connection on(listener instance) { - return assure()->on(std::move(instance)); + template + [[nodiscard]] sink before(Type *value_or_instance) { + sink other{*this}; + + if(value_or_instance) { + const auto &calls = signal->calls; + const auto it = std::find_if(calls.cbegin(), calls.cend(), [value_or_instance](const auto &delegate) { + return delegate.data() == value_or_instance; + }); + + other.offset = calls.cend() - it; + } + + return other; } /** - * @brief Registers a short-lived listener with the event emitter. - * - * This method can be used to register a listener designed to be invoked - * only once for the given event type.
- * The connection returned by the method can be freely discarded. It's meant - * to be used later to disconnect the listener if required. - * - * The listener is as a callable object that can be moved and the type of - * which is `void(const Event &, Derived &)`. - * - * @note - * Whenever an event is emitted, the emitter provides the listener with a - * reference to the derived class. Listeners don't have to capture those - * instances for later uses. - * - * @tparam Event Type of event to which to connect the listener. - * @param instance The listener to register. - * @return Connection object that can be used to disconnect the listener. + * @brief Returns a sink that connects before anything else. + * @return A properly initialized sink object. */ - template - connection once(listener instance) { - return assure()->once(std::move(instance)); + [[nodiscard]] sink before() { + sink other{*this}; + other.offset = signal->calls.size(); + return other; } /** - * @brief Disconnects a listener from the event emitter. + * @brief Connects a free function or an unbound member to a signal. * - * Do not use twice the same connection to disconnect a listener, it results - * in undefined behavior. Once used, discard the connection object. + * The signal handler performs checks to avoid multiple connections for the + * same function. * - * @tparam Event Type of event of the connection. - * @param conn A valid connection. + * @tparam Candidate Function or member to connect to the signal. + * @return A properly initialized connection object. */ - template - void erase(connection conn) ENTT_NOEXCEPT { - assure()->erase(std::move(conn)); + template + connection connect() { + disconnect(); + + delegate call{}; + call.template connect(); + signal->calls.insert(signal->calls.end() - offset, std::move(call)); + + delegate conn{}; + conn.template connect<&release>(); + return {std::move(conn), signal}; } /** - * @brief Disconnects all the listeners for the given event type. + * @brief Connects a free function with payload or a bound member to a + * signal. * - * All the connections previously returned for the given event are - * invalidated. Using them results in undefined behavior. + * 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. On the other side, the signal handler performs + * checks to avoid multiple connections for the same function.
+ * 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 Event Type of event to reset. + * @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 object that fits the purpose. + * @return A properly initialized connection object. */ - template - void clear() ENTT_NOEXCEPT { - assure()->clear(); + template + connection connect(Type &&value_or_instance) { + disconnect(value_or_instance); + + delegate call{}; + call.template connect(value_or_instance); + signal->calls.insert(signal->calls.end() - offset, std::move(call)); + + delegate conn{}; + conn.template connect<&release>(value_or_instance); + return {std::move(conn), signal}; } /** - * @brief Disconnects all the listeners. - * - * All the connections previously returned are invalidated. Using them - * results in undefined behavior. + * @brief Disconnects a free function or an unbound member from a signal. + * @tparam Candidate Function or member to disconnect from the signal. */ - void clear() ENTT_NOEXCEPT { - std::for_each(handlers.begin(), handlers.end(), [](auto &&hdata) { - return hdata.handler ? hdata.handler->clear() : void(); - }); + template + void disconnect() { + auto &calls = signal->calls; + delegate call{}; + call.template connect(); + calls.erase(std::remove(calls.begin(), calls.end(), std::move(call)), calls.end()); } /** - * @brief Checks if there are listeners registered for the specific event. - * @tparam Event Type of event to test. - * @return True if there are no listeners registered, false otherwise. + * @brief Disconnects a free function with payload or a bound member from a + * signal. + * @tparam Candidate Function or member to disconnect from the signal. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid object that fits the purpose. */ - template - bool empty() const ENTT_NOEXCEPT { - return assure()->empty(); + template + void disconnect(Type &&value_or_instance) { + auto &calls = signal->calls; + delegate call{}; + call.template connect(value_or_instance); + calls.erase(std::remove(calls.begin(), calls.end(), std::move(call)), calls.end()); } /** - * @brief Checks if there are listeners registered with the event emitter. - * @return True if there are no listeners registered, false otherwise. + * @brief Disconnects free functions with payload or bound members from a + * signal. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid object that fits the purpose. */ - bool empty() const ENTT_NOEXCEPT { - return std::all_of(handlers.cbegin(), handlers.cend(), [](auto &&hdata) { - return !hdata.handler || hdata.handler->empty(); - }); + template + void disconnect(Type &value_or_instance) { + disconnect(&value_or_instance); } -private: - mutable std::vector handlers{}; -}; + /** + * @brief Disconnects free functions with payload or bound members from a + * signal. + * @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) { + if(value_or_instance) { + auto &calls = signal->calls; + auto predicate = [value_or_instance](const auto &delegate) { return delegate.data() == value_or_instance; }; + calls.erase(std::remove_if(calls.begin(), calls.end(), std::move(predicate)), calls.end()); + } + } + /*! @brief Disconnects all the listeners from a signal. */ + void disconnect() { + signal->calls.clear(); + } -} +private: + difference_type offset; + 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>; -#endif // ENTT_SIGNAL_EMITTER_HPP +} // namespace entt -// #include "signal/sigh.hpp" +#endif diff --git a/modules/entt/src/entt/config/config.h b/modules/entt/src/entt/config/config.h index b2dcd9a..caa2871 100644 --- a/modules/entt/src/entt/config/config.h +++ b/modules/entt/src/entt/config/config.h @@ -1,44 +1,79 @@ #ifndef ENTT_CONFIG_CONFIG_H #define ENTT_CONFIG_CONFIG_H +#include "version.h" -#ifndef ENTT_NOEXCEPT -#define ENTT_NOEXCEPT noexcept -#endif // ENTT_NOEXCEPT - - -#ifndef ENTT_HS_SUFFIX -#define ENTT_HS_SUFFIX _hs -#endif // ENTT_HS_SUFFIX +#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) +# 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 +#ifndef ENTT_NOEXCEPT +# define ENTT_NOEXCEPT noexcept +# define ENTT_NOEXCEPT_IF(expr) noexcept(expr) +# else +# define ENTT_NOEXCEPT_IF(...) +#endif -#ifndef ENTT_NO_ATOMIC -#include -template -using maybe_atomic_t = std::atomic; -#else // ENTT_NO_ATOMIC -template -using maybe_atomic_t = Type; -#endif // ENTT_NO_ATOMIC - +#ifdef ENTT_USE_ATOMIC +# include +# define ENTT_MAYBE_ATOMIC(Type) std::atomic +#else +# define ENTT_MAYBE_ATOMIC(Type) Type +#endif #ifndef ENTT_ID_TYPE -#include -#define ENTT_ID_TYPE std::uint32_t -#endif // ENTT_ID_TYPE +# include +# define ENTT_ID_TYPE std::uint32_t +#endif + +#ifndef ENTT_SPARSE_PAGE +# define ENTT_SPARSE_PAGE 4096 +#endif +#ifndef ENTT_PACKED_PAGE +# define ENTT_PACKED_PAGE 1024 +#endif -#ifndef ENTT_PAGE_SIZE -#define ENTT_PAGE_SIZE 32768 -#endif // ENTT_PAGE_SIZE +#ifdef ENTT_DISABLE_ASSERT +# undef ENTT_ASSERT +# define ENTT_ASSERT(...) (void(0)) +#elif !defined ENTT_ASSERT +# include +# define ENTT_ASSERT(condition, ...) assert(condition) +#endif +#ifdef ENTT_NO_ETO +# define ENTT_IGNORE_IF_EMPTY false +#else +# define ENTT_IGNORE_IF_EMPTY true +#endif -#ifndef ENTT_DISABLE_ASSERT -#include -#define ENTT_ASSERT(condition) assert(condition) -#else // ENTT_DISABLE_ASSERT -#define ENTT_ASSERT(...) ((void)0) -#endif // ENTT_DISABLE_ASSERT +#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 +#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 -#endif // ENTT_CONFIG_CONFIG_H +#endif diff --git a/modules/entt/src/entt/config/macro.h b/modules/entt/src/entt/config/macro.h new file mode 100644 index 0000000..0fec4c7 --- /dev/null +++ b/modules/entt/src/entt/config/macro.h @@ -0,0 +1,7 @@ +#ifndef ENTT_CONFIG_MACRO_H +#define ENTT_CONFIG_MACRO_H + +#define ENTT_STR(arg) #arg +#define ENTT_XSTR(arg) ENTT_STR(arg) + +#endif diff --git a/modules/entt/src/entt/config/version.h b/modules/entt/src/entt/config/version.h index 128abe9..63c9a20 100644 --- a/modules/entt/src/entt/config/version.h +++ b/modules/entt/src/entt/config/version.h @@ -1,11 +1,14 @@ #ifndef ENTT_CONFIG_VERSION_H #define ENTT_CONFIG_VERSION_H +#include "macro.h" -#define ENTT_VERSION "3.1.0" #define ENTT_VERSION_MAJOR 3 -#define ENTT_VERSION_MINOR 1 -#define ENTT_VERSION_PATCH 0 +#define ENTT_VERSION_MINOR 10 +#define ENTT_VERSION_PATCH 3 +#define ENTT_VERSION \ + ENTT_XSTR(ENTT_VERSION_MAJOR) \ + "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH) -#endif // ENTT_CONFIG_VERSION_H +#endif diff --git a/modules/entt/src/entt/container/dense_map.hpp b/modules/entt/src/entt/container/dense_map.hpp new file mode 100644 index 0000000..19f9865 --- /dev/null +++ b/modules/entt/src/entt/container/dense_map.hpp @@ -0,0 +1,988 @@ +#ifndef ENTT_CONTAINER_DENSE_MAP_HPP +#define ENTT_CONTAINER_DENSE_MAP_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../config/config.h" +#include "../core/compressed_pair.hpp" +#include "../core/iterator.hpp" +#include "../core/memory.hpp" +#include "../core/type_traits.hpp" +#include "fwd.hpp" + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct dense_map_node final { + using value_type = std::pair; + + template + dense_map_node(const std::size_t pos, Args &&...args) + : next{pos}, + element{std::forward(args)...} {} + + template + dense_map_node(std::allocator_arg_t, const Allocator &allocator, const std::size_t pos, Args &&...args) + : next{pos}, + element{entt::make_obj_using_allocator(allocator, std::forward(args)...)} {} + + template + dense_map_node(std::allocator_arg_t, const Allocator &allocator, const dense_map_node &other) + : next{other.next}, + element{entt::make_obj_using_allocator(allocator, other.element)} {} + + template + dense_map_node(std::allocator_arg_t, const Allocator &allocator, dense_map_node &&other) + : next{other.next}, + element{entt::make_obj_using_allocator(allocator, std::move(other.element))} {} + + std::size_t next; + value_type element; +}; + +template +class dense_map_iterator final { + template + friend class dense_map_iterator; + + using first_type = decltype(std::as_const(std::declval()->element.first)); + using second_type = decltype((std::declval()->element.second)); + +public: + using value_type = std::pair; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + dense_map_iterator() ENTT_NOEXCEPT + : it{} {} + + dense_map_iterator(const It iter) ENTT_NOEXCEPT + : it{iter} {} + + template && std::is_constructible_v>> + dense_map_iterator(const dense_map_iterator &other) ENTT_NOEXCEPT + : it{other.it} {} + + dense_map_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } + + dense_map_iterator operator++(int) ENTT_NOEXCEPT { + dense_map_iterator orig = *this; + return ++(*this), orig; + } + + dense_map_iterator &operator--() ENTT_NOEXCEPT { + return --it, *this; + } + + dense_map_iterator operator--(int) ENTT_NOEXCEPT { + dense_map_iterator orig = *this; + return operator--(), orig; + } + + dense_map_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + it += value; + return *this; + } + + dense_map_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + dense_map_iterator copy = *this; + return (copy += value); + } + + dense_map_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); + } + + dense_map_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); + } + + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + return {it[value].element.first, it[value].element.second}; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return {it->element.first, it->element.second}; + } + + template + friend std::ptrdiff_t operator-(const dense_map_iterator &, const dense_map_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator==(const dense_map_iterator &, const dense_map_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator<(const dense_map_iterator &, const dense_map_iterator &) ENTT_NOEXCEPT; + +private: + It it; +}; + +template +[[nodiscard]] std::ptrdiff_t operator-(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it - rhs.it; +} + +template +[[nodiscard]] bool operator==(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] bool operator!=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +[[nodiscard]] bool operator<(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it < rhs.it; +} + +template +[[nodiscard]] bool operator>(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +template +[[nodiscard]] bool operator<=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +template +[[nodiscard]] bool operator>=(const dense_map_iterator &lhs, const dense_map_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +template +class dense_map_local_iterator final { + template + friend class dense_map_local_iterator; + + using first_type = decltype(std::as_const(std::declval()->element.first)); + using second_type = decltype((std::declval()->element.second)); + +public: + using value_type = std::pair; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + dense_map_local_iterator() ENTT_NOEXCEPT + : it{}, + offset{} {} + + dense_map_local_iterator(It iter, const std::size_t pos) ENTT_NOEXCEPT + : it{iter}, + offset{pos} {} + + template && std::is_constructible_v>> + dense_map_local_iterator(const dense_map_local_iterator &other) ENTT_NOEXCEPT + : it{other.it}, + offset{other.offset} {} + + dense_map_local_iterator &operator++() ENTT_NOEXCEPT { + return offset = it[offset].next, *this; + } + + dense_map_local_iterator operator++(int) ENTT_NOEXCEPT { + dense_map_local_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return {it[offset].element.first, it[offset].element.second}; + } + + [[nodiscard]] std::size_t index() const ENTT_NOEXCEPT { + return offset; + } + +private: + It it; + std::size_t offset; +}; + +template +[[nodiscard]] bool operator==(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() == rhs.index(); +} + +template +[[nodiscard]] bool operator!=(const dense_map_local_iterator &lhs, const dense_map_local_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @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 std::size_t minimum_capacity = 8u; + + using node_type = internal::dense_map_node; + using alloc_traits = typename std::allocator_traits; + static_assert(std::is_same_v>, "Invalid value type"); + using sparse_container_type = std::vector>; + using packed_container_type = std::vector>; + + template + [[nodiscard]] std::size_t key_to_bucket(const Other &key) const ENTT_NOEXCEPT { + return fast_mod(sparse.second()(key), bucket_count()); + } + + template + [[nodiscard]] auto constrained_find(const Other &key, std::size_t bucket) { + for(auto it = begin(bucket), last = end(bucket); it != last; ++it) { + if(packed.second()(it->first, key)) { + return begin() + static_cast(it.index()); + } + } + + return end(); + } + + template + [[nodiscard]] auto constrained_find(const Other &key, std::size_t bucket) const { + for(auto it = cbegin(bucket), last = cend(bucket); it != last; ++it) { + if(packed.second()(it->first, key)) { + return cbegin() + static_cast(it.index()); + } + } + + 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 std::make_pair(it, false); + } + + packed.first().emplace_back(sparse.first()[index], std::piecewise_construct, std::forward_as_tuple(std::forward(key)), std::forward_as_tuple(std::forward(args)...)); + sparse.first()[index] = packed.first().size() - 1u; + rehash_if_required(); + + return std::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 = std::forward(value); + return std::make_pair(it, false); + } + + packed.first().emplace_back(sparse.first()[index], std::forward(key), std::forward(value)); + sparse.first()[index] = packed.first().size() - 1u; + rehash_if_required(); + + return std::make_pair(--end(), true); + } + + void move_and_pop(const std::size_t pos) { + if(const auto last = size() - 1u; pos != last) { + packed.first()[pos] = std::move(packed.first().back()); + size_type *curr = sparse.first().data() + key_to_bucket(packed.first().back().element.first); + for(; *curr != last; curr = &packed.first()[*curr].next) {} + *curr = pos; + } + + packed.first().pop_back(); + } + + void rehash_if_required() { + if(size() > (bucket_count() * max_load_factor())) { + rehash(bucket_count() * 2u); + } + } + +public: + /*! @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 = std::pair; + /*! @brief Unsigned integer type. */ + using size_type = std::size_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 Allocator type. */ + using allocator_type = Allocator; + /*! @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 bucket_count Minimal number of buckets. + * @param allocator The allocator to use. + */ + dense_map(const size_type bucket_count, const allocator_type &allocator) + : dense_map{bucket_count, hasher{}, key_equal{}, allocator} {} + + /** + * @brief Constructs an empty container with a given allocator, hash + * function and user supplied minimal number of buckets. + * @param bucket_count Minimal number of buckets. + * @param hash Hash function to use. + * @param allocator The allocator to use. + */ + dense_map(const size_type bucket_count, const hasher &hash, const allocator_type &allocator) + : dense_map{bucket_count, 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 bucket_count 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 bucket_count, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{}) + : sparse{allocator, hash}, + packed{allocator, equal}, + threshold{default_threshold} { + rehash(bucket_count); + } + + /*! @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{std::piecewise_construct, std::forward_as_tuple(other.sparse.first(), allocator), std::forward_as_tuple(other.sparse.second())}, + packed{std::piecewise_construct, std::forward_as_tuple(other.packed.first(), allocator), std::forward_as_tuple(other.packed.second())}, + threshold{other.threshold} {} + + /*! @brief Default move constructor. */ + dense_map(dense_map &&) = 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{std::piecewise_construct, std::forward_as_tuple(std::move(other.sparse.first()), allocator), std::forward_as_tuple(std::move(other.sparse.second()))}, + packed{std::piecewise_construct, std::forward_as_tuple(std::move(other.packed.first()), allocator), std::forward_as_tuple(std::move(other.packed.second()))}, + threshold{other.threshold} {} + + /** + * @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 &&) = default; + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return sparse.first().get_allocator(); + } + + /** + * @brief Returns an iterator to the beginning. + * + * The returned iterator points to the first instance of the internal array. + * 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 ENTT_NOEXCEPT { + return packed.first().begin(); + } + + /*! @copydoc cbegin */ + [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT { + return cbegin(); + } + + /*! @copydoc begin */ + [[nodiscard]] iterator begin() ENTT_NOEXCEPT { + return packed.first().begin(); + } + + /** + * @brief Returns an iterator to the end. + * + * The returned iterator points to the element following the last instance + * of the internal array. Attempting to dereference the returned iterator + * results in undefined behavior. + * + * @return An iterator to the element following the last instance of the + * internal array. + */ + [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { + return packed.first().end(); + } + + /*! @copydoc cend */ + [[nodiscard]] const_iterator end() const ENTT_NOEXCEPT { + return cend(); + } + + /*! @copydoc end */ + [[nodiscard]] iterator end() ENTT_NOEXCEPT { + return packed.first().end(); + } + + /** + * @brief Checks whether a container is empty. + * @return True if the container is empty, false otherwise. + */ + [[nodiscard]] bool empty() const ENTT_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 ENTT_NOEXCEPT { + return packed.first().size(); + } + + /*! @brief Clears the container. */ + void clear() ENTT_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. + */ + std::pair insert(const value_type &value) { + return insert_or_do_nothing(value.first, value.second); + } + + /*! @copydoc insert */ + std::pair insert(value_type &&value) { + return insert_or_do_nothing(std::move(value.first), std::move(value.second)); + } + + /** + * @copydoc insert + * @tparam Arg Type of the key-value pair to insert into the container. + */ + template + std::enable_if_t, std::pair> + insert(Arg &&value) { + return insert_or_do_nothing(std::forward(value).first, std::forward(value).second); + } + + /** + * @brief Inserts elements into the container, if their keys do not exist. + * @tparam It Type of input iterator. + * @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. + */ + template + void insert(It first, It 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 + std::pair insert_or_assign(const key_type &key, Arg &&value) { + return insert_or_overwrite(key, std::forward(value)); + } + + /*! @copydoc insert_or_assign */ + template + std::pair insert_or_assign(key_type &&key, Arg &&value) { + return insert_or_overwrite(std::move(key), std::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 + std::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(std::forward(args).first..., std::forward(args).second...); + } else if constexpr(sizeof...(Args) == 2u) { + return insert_or_do_nothing(std::forward(args)...); + } else { + auto &node = packed.first().emplace_back(packed.first().size(), std::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 std::make_pair(it, false); + } + + std::swap(node.next, sparse.first()[index]); + rehash_if_required(); + + return std::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 + std::pair try_emplace(const key_type &key, Args &&...args) { + return insert_or_do_nothing(key, std::forward(args)...); + } + + /*! @copydoc try_emplace */ + template + std::pair try_emplace(key_type &&key, Args &&...args) { + return insert_or_do_nothing(std::move(key), std::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()[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().data() + key_to_bucket(key); *curr != (std::numeric_limits::max)(); 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 Exchanges the contents with those of a given container. + * @param other Container to exchange the content with. + */ + void swap(dense_map &other) { + using std::swap; + swap(sparse, other.sparse); + swap(packed, other.packed); + swap(threshold, other.threshold); + } + + /** + * @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 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(std::move(key)).first->second; + } + + /** + * @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 + * value. + * @tparam Other Type of the key value of an element to search for. + * @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. + */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + find(const Other &key) { + return constrained_find(key, key_to_bucket(key)); + } + + /*! @copydoc find */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + find(const Other &key) const { + return constrained_find(key, key_to_bucket(key)); + } + + /** + * @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. + * @tparam Other Type of the key value of an element to search for. + * @param key Key value of an element to search for. + * @return True if there is such an element, false otherwise. + */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + contains(const Other &key) const { + 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().begin(), 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().begin(), 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 {packed.first().begin(), (std::numeric_limits::max)()}; + } + + /** + * @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([[maybe_unused]] 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 {packed.first().begin(), (std::numeric_limits::max)()}; + } + + /** + * @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(std::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 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 count New number of buckets. + */ + void rehash(const size_type count) { + auto value = (std::max)(count, minimum_capacity); + value = (std::max)(value, static_cast(size() / max_load_factor())); + + if(const auto sz = next_power_of_two(value); sz != bucket_count()) { + sparse.first().resize(sz); + std::fill(sparse.first().begin(), sparse.first().end(), (std::numeric_limits::max)()); + + for(size_type pos{}, last = size(); pos < last; ++pos) { + const auto index = key_to_bucket(packed.first()[pos].element.first); + packed.first()[pos].next = std::exchange(sparse.first()[index], pos); + } + } + } + + /** + * @brief Reserves space for at least the specified number of elements and + * regenerates the hash table. + * @param count New number of elements. + */ + void reserve(const size_type count) { + packed.first().reserve(count); + rehash(static_cast(std::ceil(count / 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; +}; + +} // namespace entt + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace std { + +template +struct uses_allocator, Allocator> + : std::true_type {}; + +} // namespace std + +/** + * Internal details not to be documented. + * @endcond + */ + +#endif diff --git a/modules/entt/src/entt/container/dense_set.hpp b/modules/entt/src/entt/container/dense_set.hpp new file mode 100644 index 0000000..55f0a5c --- /dev/null +++ b/modules/entt/src/entt/container/dense_set.hpp @@ -0,0 +1,823 @@ +#ifndef ENTT_CONTAINER_DENSE_SET_HPP +#define ENTT_CONTAINER_DENSE_SET_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../config/config.h" +#include "../core/compressed_pair.hpp" +#include "../core/memory.hpp" +#include "../core/type_traits.hpp" +#include "fwd.hpp" + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +class dense_set_iterator final { + template + friend class dense_set_iterator; + +public: + using value_type = typename It::value_type::second_type; + using pointer = const value_type *; + using reference = const value_type &; + using difference_type = std::ptrdiff_t; + using iterator_category = std::random_access_iterator_tag; + + dense_set_iterator() ENTT_NOEXCEPT + : it{} {} + + dense_set_iterator(const It iter) ENTT_NOEXCEPT + : it{iter} {} + + template && std::is_constructible_v>> + dense_set_iterator(const dense_set_iterator &other) ENTT_NOEXCEPT + : it{other.it} {} + + dense_set_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } + + dense_set_iterator operator++(int) ENTT_NOEXCEPT { + dense_set_iterator orig = *this; + return ++(*this), orig; + } + + dense_set_iterator &operator--() ENTT_NOEXCEPT { + return --it, *this; + } + + dense_set_iterator operator--(int) ENTT_NOEXCEPT { + dense_set_iterator orig = *this; + return operator--(), orig; + } + + dense_set_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + it += value; + return *this; + } + + dense_set_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + dense_set_iterator copy = *this; + return (copy += value); + } + + dense_set_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); + } + + dense_set_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); + } + + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + return it[value].second; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return std::addressof(it->second); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return *operator->(); + } + + template + friend std::ptrdiff_t operator-(const dense_set_iterator &, const dense_set_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator==(const dense_set_iterator &, const dense_set_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator<(const dense_set_iterator &, const dense_set_iterator &) ENTT_NOEXCEPT; + +private: + It it; +}; + +template +[[nodiscard]] std::ptrdiff_t operator-(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it - rhs.it; +} + +template +[[nodiscard]] bool operator==(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] bool operator!=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +[[nodiscard]] bool operator<(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it < rhs.it; +} + +template +[[nodiscard]] bool operator>(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +template +[[nodiscard]] bool operator<=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +template +[[nodiscard]] bool operator>=(const dense_set_iterator &lhs, const dense_set_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +template +class dense_set_local_iterator final { + template + friend class dense_set_local_iterator; + +public: + using value_type = typename It::value_type::second_type; + using pointer = const value_type *; + using reference = const value_type &; + using difference_type = std::ptrdiff_t; + using iterator_category = std::forward_iterator_tag; + + dense_set_local_iterator() ENTT_NOEXCEPT + : it{}, + offset{} {} + + dense_set_local_iterator(It iter, const std::size_t pos) ENTT_NOEXCEPT + : it{iter}, + offset{pos} {} + + template && std::is_constructible_v>> + dense_set_local_iterator(const dense_set_local_iterator &other) ENTT_NOEXCEPT + : it{other.it}, + offset{other.offset} {} + + dense_set_local_iterator &operator++() ENTT_NOEXCEPT { + return offset = it[offset].first, *this; + } + + dense_set_local_iterator operator++(int) ENTT_NOEXCEPT { + dense_set_local_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return std::addressof(it[offset].second); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return *operator->(); + } + + [[nodiscard]] std::size_t index() const ENTT_NOEXCEPT { + return offset; + } + +private: + It it; + std::size_t offset; +}; + +template +[[nodiscard]] bool operator==(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() == rhs.index(); +} + +template +[[nodiscard]] bool operator!=(const dense_set_local_iterator &lhs, const dense_set_local_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @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 std::size_t minimum_capacity = 8u; + + using node_type = std::pair; + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using sparse_container_type = std::vector>; + using packed_container_type = std::vector>; + + template + [[nodiscard]] std::size_t value_to_bucket(const Other &value) const ENTT_NOEXCEPT { + return fast_mod(sparse.second()(value), bucket_count()); + } + + template + [[nodiscard]] auto constrained_find(const Other &value, std::size_t bucket) { + for(auto it = begin(bucket), last = end(bucket); it != last; ++it) { + if(packed.second()(*it, value)) { + return begin() + static_cast(it.index()); + } + } + + return end(); + } + + template + [[nodiscard]] auto constrained_find(const Other &value, std::size_t bucket) const { + for(auto it = cbegin(bucket), last = cend(bucket); it != last; ++it) { + if(packed.second()(*it, value)) { + return cbegin() + static_cast(it.index()); + } + } + + 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 std::make_pair(it, false); + } + + packed.first().emplace_back(sparse.first()[index], std::forward(value)); + sparse.first()[index] = packed.first().size() - 1u; + rehash_if_required(); + + return std::make_pair(--end(), true); + } + + void move_and_pop(const std::size_t pos) { + if(const auto last = size() - 1u; pos != last) { + packed.first()[pos] = std::move(packed.first().back()); + size_type *curr = sparse.first().data() + value_to_bucket(packed.first().back().second); + for(; *curr != last; curr = &packed.first()[*curr].first) {} + *curr = pos; + } + + packed.first().pop_back(); + } + + void rehash_if_required() { + if(size() > (bucket_count() * max_load_factor())) { + rehash(bucket_count() * 2u); + } + } + +public: + /*! @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 = std::size_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 Allocator type. */ + using allocator_type = Allocator; + /*! @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 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 bucket_count Minimal number of buckets. + * @param allocator The allocator to use. + */ + dense_set(const size_type bucket_count, const allocator_type &allocator) + : dense_set{bucket_count, hasher{}, key_equal{}, allocator} {} + + /** + * @brief Constructs an empty container with a given allocator, hash + * function and user supplied minimal number of buckets. + * @param bucket_count Minimal number of buckets. + * @param hash Hash function to use. + * @param allocator The allocator to use. + */ + dense_set(const size_type bucket_count, const hasher &hash, const allocator_type &allocator) + : dense_set{bucket_count, 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 bucket_count 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 bucket_count, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{}) + : sparse{allocator, hash}, + packed{allocator, equal}, + threshold{default_threshold} { + rehash(bucket_count); + } + + /*! @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{std::piecewise_construct, std::forward_as_tuple(other.sparse.first(), allocator), std::forward_as_tuple(other.sparse.second())}, + packed{std::piecewise_construct, std::forward_as_tuple(other.packed.first(), allocator), std::forward_as_tuple(other.packed.second())}, + threshold{other.threshold} {} + + /*! @brief Default move constructor. */ + dense_set(dense_set &&) = 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{std::piecewise_construct, std::forward_as_tuple(std::move(other.sparse.first()), allocator), std::forward_as_tuple(std::move(other.sparse.second()))}, + packed{std::piecewise_construct, std::forward_as_tuple(std::move(other.packed.first()), allocator), std::forward_as_tuple(std::move(other.packed.second()))}, + threshold{other.threshold} {} + + /** + * @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 &&) = default; + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return sparse.first().get_allocator(); + } + + /** + * @brief Returns an iterator to the beginning. + * + * The returned iterator points to the first instance of the internal array. + * 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 ENTT_NOEXCEPT { + return packed.first().begin(); + } + + /*! @copydoc cbegin */ + [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT { + return cbegin(); + } + + /*! @copydoc begin */ + [[nodiscard]] iterator begin() ENTT_NOEXCEPT { + return packed.first().begin(); + } + + /** + * @brief Returns an iterator to the end. + * + * The returned iterator points to the element following the last instance + * of the internal array. Attempting to dereference the returned iterator + * results in undefined behavior. + * + * @return An iterator to the element following the last instance of the + * internal array. + */ + [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { + return packed.first().end(); + } + + /*! @copydoc cend */ + [[nodiscard]] const_iterator end() const ENTT_NOEXCEPT { + return cend(); + } + + /*! @copydoc end */ + [[nodiscard]] iterator end() ENTT_NOEXCEPT { + return packed.first().end(); + } + + /** + * @brief Checks whether a container is empty. + * @return True if the container is empty, false otherwise. + */ + [[nodiscard]] bool empty() const ENTT_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 ENTT_NOEXCEPT { + return packed.first().size(); + } + + /*! @brief Clears the container. */ + void clear() ENTT_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. + */ + std::pair insert(const value_type &value) { + return insert_or_do_nothing(value); + } + + /*! @copydoc insert */ + std::pair insert(value_type &&value) { + return insert_or_do_nothing(std::move(value)); + } + + /** + * @brief Inserts elements into the container, if they do not exist. + * @tparam It Type of input iterator. + * @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. + */ + template + void insert(It first, It 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 + std::pair emplace(Args &&...args) { + if constexpr(((sizeof...(Args) == 1u) && ... && std::is_same_v>, value_type>)) { + return insert_or_do_nothing(std::forward(args)...); + } else { + auto &node = packed.first().emplace_back(std::piecewise_construct, std::make_tuple(packed.first().size()), std::forward_as_tuple(std::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 std::make_pair(it, false); + } + + std::swap(node.first, sparse.first()[index]); + rehash_if_required(); + + return std::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()[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().data() + value_to_bucket(value); *curr != (std::numeric_limits::max)(); 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 Exchanges the contents with those of a given container. + * @param other Container to exchange the content with. + */ + void swap(dense_set &other) { + using std::swap; + swap(sparse, other.sparse); + swap(packed, other.packed); + swap(threshold, other.threshold); + } + + /** + * @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. + * @tparam Other Type of an element to search for. + * @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. + */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + find(const Other &value) { + return constrained_find(value, value_to_bucket(value)); + } + + /*! @copydoc find */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + find(const Other &value) const { + return constrained_find(value, value_to_bucket(value)); + } + + /** + * @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. + * @tparam Other Type of an element to search for. + * @param value Value of an element to search for. + * @return True if there is such an element, false otherwise. + */ + template + [[nodiscard]] std::enable_if_t && is_transparent_v, std::conditional_t> + contains(const Other &value) const { + 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().begin(), 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().begin(), 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 {packed.first().begin(), (std::numeric_limits::max)()}; + } + + /** + * @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([[maybe_unused]] 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 {packed.first().begin(), (std::numeric_limits::max)()}; + } + + /** + * @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(std::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 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 count New number of buckets. + */ + void rehash(const size_type count) { + auto value = (std::max)(count, minimum_capacity); + value = (std::max)(value, static_cast(size() / max_load_factor())); + + if(const auto sz = next_power_of_two(value); sz != bucket_count()) { + sparse.first().resize(sz); + std::fill(sparse.first().begin(), sparse.first().end(), (std::numeric_limits::max)()); + + for(size_type pos{}, last = size(); pos < last; ++pos) { + const auto index = value_to_bucket(packed.first()[pos].second); + packed.first()[pos].first = std::exchange(sparse.first()[index], pos); + } + } + } + + /** + * @brief Reserves space for at least the specified number of elements and + * regenerates the hash table. + * @param count New number of elements. + */ + void reserve(const size_type count) { + packed.first().reserve(count); + rehash(static_cast(std::ceil(count / 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; +}; + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/container/fwd.hpp b/modules/entt/src/entt/container/fwd.hpp new file mode 100644 index 0000000..eb8098d --- /dev/null +++ b/modules/entt/src/entt/container/fwd.hpp @@ -0,0 +1,26 @@ +#ifndef ENTT_CONTAINER_FWD_HPP +#define ENTT_CONTAINER_FWD_HPP + +#include +#include + +namespace entt { + +template< + typename Key, + typename Type, + typename = std::hash, + typename = std::equal_to, + typename = std::allocator>> +class dense_map; + +template< + typename Type, + typename = std::hash, + typename = std::equal_to, + typename = std::allocator> +class dense_set; + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/core/algorithm.hpp b/modules/entt/src/entt/core/algorithm.hpp index 41b5ef7..44c47ef 100644 --- a/modules/entt/src/entt/core/algorithm.hpp +++ b/modules/entt/src/entt/core/algorithm.hpp @@ -1,15 +1,15 @@ #ifndef ENTT_CORE_ALGORITHM_HPP #define ENTT_CORE_ALGORITHM_HPP - -#include #include +#include +#include #include - +#include +#include "utility.hpp" namespace entt { - /** * @brief Function object to wrap `std::sort` in a class type. * @@ -33,12 +33,11 @@ struct std_sort { * @param args Arguments to forward to the sort function, if any. */ template, typename... Args> - void operator()(It first, It last, Compare compare = Compare{}, Args &&... args) const { + void operator()(It first, It last, Compare compare = Compare{}, Args &&...args) const { std::sort(std::forward(args)..., std::move(first), std::move(last), std::move(compare)); } }; - /*! @brief Function object for performing insertion sort. */ struct insertion_sort { /** @@ -54,25 +53,85 @@ struct insertion_sort { */ template> void operator()(It first, It last, Compare compare = Compare{}) const { - if(first != last) { - auto it = first + 1; - - while(it != last) { - auto pre = it++; - auto value = *pre; + if(first < last) { + for(auto it = first + 1; it < last; ++it) { + auto value = std::move(*it); + auto pre = it; - while(pre-- != first && compare(value, *pre)) { - *(pre+1) = *pre; + for(; pre > first && compare(value, *(pre - 1)); --pre) { + *pre = std::move(*(pre - 1)); } - *(pre+1) = value; + *pre = std::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 +struct radix_sort { + static_assert((N % Bit) == 0, "The maximum number of bits to sort must be a multiple of the number of bits processed per pass"); -} + /** + * @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) { + static constexpr auto mask = (1 << Bit) - 1; + static constexpr auto buckets = 1 << Bit; + static constexpr auto passes = N / Bit; + + using value_type = typename std::iterator_traits::value_type; + std::vector aux(std::distance(first, last)); + + auto part = [getter = std::move(getter)](auto from, auto to, auto out, auto start) { + std::size_t index[buckets]{}; + std::size_t count[buckets]{}; + + for(auto it = from; it != to; ++it) { + ++count[(getter(*it) >> start) & mask]; + } + + for(std::size_t pos{}, end = buckets - 1u; pos < end; ++pos) { + index[pos + 1u] = index[pos] + count[pos]; + } + + for(auto it = from; it != to; ++it) { + out[index[(getter(*it) >> start) & mask]++] = std::move(*it); + } + }; + + for(std::size_t pass = 0; pass < (passes & ~1); 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); + std::move(aux.begin(), aux.end(), first); + } + } + } +}; +} // namespace entt -#endif // ENTT_CORE_ALGORITHM_HPP +#endif diff --git a/modules/entt/src/entt/core/any.hpp b/modules/entt/src/entt/core/any.hpp new file mode 100644 index 0000000..211df2f --- /dev/null +++ b/modules/entt/src/entt/core/any.hpp @@ -0,0 +1,490 @@ +#ifndef ENTT_CORE_ANY_HPP +#define ENTT_CORE_ANY_HPP + +#include +#include +#include +#include +#include "../config/config.h" +#include "../core/utility.hpp" +#include "fwd.hpp" +#include "type_info.hpp" +#include "type_traits.hpp" + +namespace entt { + +/** + * @brief A SBO friendly, type-safe container for single values of any type. + * @tparam Len Size of the storage reserved for the small buffer optimization. + * @tparam Align Optional alignment requirement. + */ +template +class basic_any { + enum class operation : std::uint8_t { + copy, + move, + transfer, + assign, + destroy, + compare, + get + }; + + enum class policy : std::uint8_t { + owner, + ref, + cref + }; + + using storage_type = std::aligned_storage_t; + using vtable_type = const void *(const operation, const basic_any &, const void *); + + template + static constexpr bool in_situ = Len && alignof(Type) <= alignof(storage_type) && sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v; + + template + static const void *basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const basic_any &value, [[maybe_unused]] const void *other) { + static_assert(!std::is_same_v && std::is_same_v>, Type>, "Invalid type"); + const Type *element = nullptr; + + if constexpr(in_situ) { + element = value.owner() ? reinterpret_cast(&value.storage) : static_cast(value.instance); + } else { + element = static_cast(value.instance); + } + + switch(op) { + case operation::copy: + if constexpr(std::is_copy_constructible_v) { + static_cast(const_cast(other))->initialize(*element); + } + break; + case operation::move: + if constexpr(in_situ) { + if(value.owner()) { + return new(&static_cast(const_cast(other))->storage) Type{std::move(*const_cast(element))}; + } + } + + return (static_cast(const_cast(other))->instance = std::exchange(const_cast(value).instance, nullptr)); + case operation::transfer: + if constexpr(std::is_move_assignable_v) { + *const_cast(element) = std::move(*static_cast(const_cast(other))); + return other; + } + [[fallthrough]]; + case operation::assign: + if constexpr(std::is_copy_assignable_v) { + *const_cast(element) = *static_cast(other); + return other; + } + break; + case operation::destroy: + if constexpr(in_situ) { + element->~Type(); + } else if constexpr(std::is_array_v) { + delete[] element; + } else { + delete element; + } + break; + case operation::compare: + if constexpr(!std::is_function_v && !std::is_array_v && is_equality_comparable_v) { + return *static_cast(element) == *static_cast(other) ? other : nullptr; + } else { + return (element == other) ? other : nullptr; + } + case operation::get: + return element; + } + + return nullptr; + } + + template + void initialize([[maybe_unused]] Args &&...args) { + if constexpr(!std::is_void_v) { + info = &type_id>>(); + vtable = basic_vtable>>; + + if constexpr(std::is_lvalue_reference_v) { + static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v && ...), "Invalid arguments"); + mode = std::is_const_v> ? policy::cref : policy::ref; + instance = (std::addressof(args), ...); + } else if constexpr(in_situ) { + if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v) { + new(&storage) Type{std::forward(args)...}; + } else { + new(&storage) Type(std::forward(args)...); + } + } else { + if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v) { + instance = new Type{std::forward(args)...}; + } else { + instance = new Type(std::forward(args)...); + } + } + } + } + + basic_any(const basic_any &other, const policy pol) ENTT_NOEXCEPT + : instance{other.data()}, + info{other.info}, + vtable{other.vtable}, + mode{pol} {} + +public: + /*! @brief Size of the internal storage. */ + static constexpr auto length = Len; + /*! @brief Alignment requirement. */ + static constexpr auto alignment = Align; + + /*! @brief Default constructor. */ + constexpr basic_any() ENTT_NOEXCEPT + : instance{}, + info{&type_id()}, + vtable{}, + mode{policy::owner} {} + + /** + * @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(std::in_place_type_t, Args &&...args) + : basic_any{} { + initialize(std::forward(args)...); + } + + /** + * @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, basic_any>>> + basic_any(Type &&value) + : basic_any{} { + initialize>(std::forward(value)); + } + + /** + * @brief Copy constructor. + * @param other The instance to copy from. + */ + basic_any(const basic_any &other) + : basic_any{} { + if(other.vtable) { + other.vtable(operation::copy, other, this); + } + } + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_any(basic_any &&other) ENTT_NOEXCEPT + : instance{}, + info{other.info}, + vtable{other.vtable}, + mode{other.mode} { + if(other.vtable) { + other.vtable(operation::move, other, this); + } + } + + /*! @brief Frees the internal storage, whatever it means. */ + ~basic_any() { + if(vtable && owner()) { + vtable(operation::destroy, *this, nullptr); + } + } + + /** + * @brief Copy assignment operator. + * @param other The instance to copy from. + * @return This any object. + */ + basic_any &operator=(const basic_any &other) { + reset(); + + if(other.vtable) { + other.vtable(operation::copy, other, this); + } + + return *this; + } + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This any object. + */ + basic_any &operator=(basic_any &&other) ENTT_NOEXCEPT { + reset(); + + if(other.vtable) { + other.vtable(operation::move, other, this); + info = other.info; + vtable = other.vtable; + 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 + std::enable_if_t, basic_any>, basic_any &> + operator=(Type &&value) { + emplace>(std::forward(value)); + return *this; + } + + /** + * @brief Returns the object type if any, `type_id()` otherwise. + * @return The object type if any, `type_id()` otherwise. + */ + [[nodiscard]] const type_info &type() const ENTT_NOEXCEPT { + return *info; + } + + /** + * @brief Returns an opaque pointer to the contained instance. + * @return An opaque pointer the contained instance, if any. + */ + [[nodiscard]] const void *data() const ENTT_NOEXCEPT { + return vtable ? vtable(operation::get, *this, nullptr) : nullptr; + } + + /** + * @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 ENTT_NOEXCEPT { + return *info == req ? data() : nullptr; + } + + /** + * @brief Returns an opaque pointer to the contained instance. + * @return An opaque pointer the contained instance, if any. + */ + [[nodiscard]] void *data() ENTT_NOEXCEPT { + return (!vtable || mode == policy::cref) ? nullptr : const_cast(vtable(operation::get, *this, nullptr)); + } + + /** + * @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) ENTT_NOEXCEPT { + return *info == req ? data() : nullptr; + } + + /** + * @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) { + reset(); + initialize(std::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 any &other) { + if(vtable && mode != policy::cref && *info == *other.info) { + return (vtable(operation::assign, *this, other.data()) != nullptr); + } + + return false; + } + + /*! @copydoc assign */ + bool assign(any &&other) { + if(vtable && mode != policy::cref && *info == *other.info) { + if(auto *val = other.data(); val) { + return (vtable(operation::transfer, *this, val) != nullptr); + } else { + return (vtable(operation::assign, *this, std::as_const(other).data()) != nullptr); + } + } + + return false; + } + + /*! @brief Destroys contained object */ + void reset() { + if(vtable && owner()) { + vtable(operation::destroy, *this, nullptr); + } + + info = &type_id(); + vtable = nullptr; + mode = policy::owner; + } + + /** + * @brief Returns false if a wrapper is empty, true otherwise. + * @return False if the wrapper is empty, true otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return vtable != nullptr; + } + + /** + * @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. + */ + bool operator==(const basic_any &other) const ENTT_NOEXCEPT { + if(vtable && *info == *other.info) { + return (vtable(operation::compare, *this, other.data()) != nullptr); + } + + return (!vtable && !other.vtable); + } + + /** + * @brief Aliasing constructor. + * @return A wrapper that shares a reference to an unmanaged object. + */ + [[nodiscard]] basic_any as_ref() ENTT_NOEXCEPT { + return basic_any{*this, (mode == policy::cref ? policy::cref : policy::ref)}; + } + + /*! @copydoc as_ref */ + [[nodiscard]] basic_any as_ref() const ENTT_NOEXCEPT { + return basic_any{*this, policy::cref}; + } + + /** + * @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 ENTT_NOEXCEPT { + return (mode == policy::owner); + } + +private: + union { + const void *instance; + storage_type storage; + }; + const type_info *info; + vtable_type *vtable; + policy mode; +}; + +/** + * @brief Checks if two wrappers differ in their content. + * @tparam Len Size of the storage reserved for the small buffer optimization. + * @tparam Align Alignment requirement. + * @param lhs A wrapper, either empty or not. + * @param rhs A wrapper, either empty or not. + * @return True if the two wrappers differ in their content, false otherwise. + */ +template +[[nodiscard]] inline bool operator!=(const basic_any &lhs, const basic_any &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Performs type-safe access to the contained object. + * @tparam Type Type to which conversion is required. + * @tparam Len Size of the storage reserved for the small buffer optimization. + * @tparam Align Alignment requirement. + * @param data Target any object. + * @return The element converted to the requested type. + */ +template +Type any_cast(const basic_any &data) ENTT_NOEXCEPT { + const auto *const instance = any_cast>(&data); + ENTT_ASSERT(instance, "Invalid instance"); + return static_cast(*instance); +} + +/*! @copydoc any_cast */ +template +Type any_cast(basic_any &data) ENTT_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 +Type any_cast(basic_any &&data) ENTT_NOEXCEPT { + if constexpr(std::is_copy_constructible_v>>) { + if(auto *const instance = any_cast>(&data); instance) { + return static_cast(std::move(*instance)); + } else { + return any_cast(data); + } + } else { + auto *const instance = any_cast>(&data); + ENTT_ASSERT(instance, "Invalid instance"); + return static_cast(std::move(*instance)); + } +} + +/*! @copydoc any_cast */ +template +const Type *any_cast(const basic_any *data) ENTT_NOEXCEPT { + const auto &info = type_id>>(); + return static_cast(data->data(info)); +} + +/*! @copydoc any_cast */ +template +Type *any_cast(basic_any *data) ENTT_NOEXCEPT { + const auto &info = type_id>>(); + // last attempt to make wrappers for const references return their values + return static_cast(static_cast, Type> *>(data)->data(info)); +} + +/** + * @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 storage 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, std::size_t Align = basic_any::alignment, typename... Args> +basic_any make_any(Args &&...args) { + return basic_any{std::in_place_type, std::forward(args)...}; +} + +/** + * @brief Forwards its argument and avoids copies for lvalue references. + * @tparam Len Size of the storage 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, std::size_t Align = basic_any::alignment, typename Type> +basic_any forward_as_any(Type &&value) { + return basic_any{std::in_place_type, std::decay_t, Type>>, std::forward(value)}; +} + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/core/attribute.h b/modules/entt/src/entt/core/attribute.h new file mode 100644 index 0000000..b1d0503 --- /dev/null +++ b/modules/entt/src/entt/core/attribute.h @@ -0,0 +1,30 @@ +#ifndef ENTT_CORE_ATTRIBUTE_H +#define ENTT_CORE_ATTRIBUTE_H + +#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 + +#endif diff --git a/modules/entt/src/entt/core/compressed_pair.hpp b/modules/entt/src/entt/core/compressed_pair.hpp new file mode 100644 index 0000000..6222ca1 --- /dev/null +++ b/modules/entt/src/entt/core/compressed_pair.hpp @@ -0,0 +1,280 @@ +#ifndef ENTT_CORE_COMPRESSED_PAIR_HPP +#define ENTT_CORE_COMPRESSED_PAIR_HPP + +#include +#include +#include +#include +#include "../config/config.h" +#include "type_traits.hpp" + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct compressed_pair_element { + using reference = Type &; + using const_reference = const Type &; + + template>> + compressed_pair_element() + : value{} {} + + template>, compressed_pair_element>>> + compressed_pair_element(Args &&args) + : value{std::forward(args)} {} + + template + compressed_pair_element(std::tuple args, std::index_sequence) + : value{std::forward(std::get(args))...} {} + + [[nodiscard]] reference get() ENTT_NOEXCEPT { + return value; + } + + [[nodiscard]] const_reference get() const ENTT_NOEXCEPT { + return value; + } + +private: + Type value; +}; + +template +struct compressed_pair_element>>: Type { + using reference = Type &; + using const_reference = const Type &; + using base_type = Type; + + template>> + compressed_pair_element() + : base_type{} {} + + template>, compressed_pair_element>>> + compressed_pair_element(Args &&args) + : base_type{std::forward(args)} {} + + template + compressed_pair_element(std::tuple args, std::index_sequence) + : base_type{std::forward(std::get(args))...} {} + + [[nodiscard]] reference get() ENTT_NOEXCEPT { + return *this; + } + + [[nodiscard]] const_reference get() const ENTT_NOEXCEPT { + return *this; + } +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @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. + * + * @tparam Dummy Dummy template parameter used for internal purposes. + */ + template && std::is_default_constructible_v>> + constexpr compressed_pair() + : 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) = 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) + : first_base{std::forward(arg)}, + second_base{std::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(std::piecewise_construct_t, std::tuple args, std::tuple other) + : first_base{std::move(args), std::index_sequence_for{}}, + second_base{std::move(other), std::index_sequence_for{}} {} + + /** + * @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) = default; + + /** + * @brief Returns the first element that a pair stores. + * @return The first element that a pair stores. + */ + [[nodiscard]] first_type &first() ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /*! @copydoc first */ + [[nodiscard]] const first_type &first() const ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /** + * @brief Returns the second element that a pair stores. + * @return The second element that a pair stores. + */ + [[nodiscard]] second_type &second() ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /*! @copydoc second */ + [[nodiscard]] const second_type &second() const ENTT_NOEXCEPT { + return static_cast(*this).get(); + } + + /** + * @brief Swaps two compressed pair objects. + * @param other The compressed pair to swap with. + */ + void swap(compressed_pair &other) { + using std::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 + decltype(auto) get() ENTT_NOEXCEPT { + if constexpr(Index == 0u) { + return first(); + } else { + static_assert(Index == 1u, "Index out of bounds"); + return second(); + } + } + + /*! @copydoc get */ + template + decltype(auto) get() const ENTT_NOEXCEPT { + if constexpr(Index == 0u) { + return first(); + } else { + static_assert(Index == 1u, "Index out of bounds"); + 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, std::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 +inline void swap(compressed_pair &lhs, compressed_pair &rhs) { + lhs.swap(rhs); +} + +} // namespace entt + +// disable structured binding support for clang 6, it messes when specializing tuple_size +#if !defined __clang_major__ || __clang_major__ > 6 +namespace std { + +/** + * @brief `std::tuple_size` specialization for `compressed_pair`s. + * @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 +struct tuple_size>: integral_constant {}; + +/** + * @brief `std::tuple_element` specialization for `compressed_pair`s. + * @tparam Index The index of the type to return. + * @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 +struct tuple_element>: conditional { + static_assert(Index < 2u, "Index out of bounds"); +}; + +} // namespace std +#endif + +#endif diff --git a/modules/entt/src/entt/core/enum.hpp b/modules/entt/src/entt/core/enum.hpp new file mode 100644 index 0000000..8b19f7f --- /dev/null +++ b/modules/entt/src/entt/core/enum.hpp @@ -0,0 +1,98 @@ +#ifndef ENTT_CORE_ENUM_HPP +#define ENTT_CORE_ENUM_HPP + +#include +#include "../config/config.h" + +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: std::false_type {}; + +/*! @copydoc enum_as_bitmask */ +template +struct enum_as_bitmask>: std::is_enum {}; + +/** + * @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; + +} // 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 std::enable_if_t, Type> +operator|(const Type lhs, const Type rhs) ENTT_NOEXCEPT { + return static_cast(static_cast>(lhs) | static_cast>(rhs)); +} + +/*! @copydoc operator| */ +template +[[nodiscard]] constexpr std::enable_if_t, Type> +operator&(const Type lhs, const Type rhs) ENTT_NOEXCEPT { + return static_cast(static_cast>(lhs) & static_cast>(rhs)); +} + +/*! @copydoc operator| */ +template +[[nodiscard]] constexpr std::enable_if_t, Type> +operator^(const Type lhs, const Type rhs) ENTT_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 std::enable_if_t, Type> +operator~(const Type value) ENTT_NOEXCEPT { + return static_cast(~static_cast>(value)); +} + +/*! @copydoc operator~ */ +template +[[nodiscard]] constexpr std::enable_if_t, bool> +operator!(const Type value) ENTT_NOEXCEPT { + return !static_cast>(value); +} + +/*! @copydoc operator| */ +template +constexpr std::enable_if_t, Type &> +operator|=(Type &lhs, const Type rhs) ENTT_NOEXCEPT { + return (lhs = (lhs | rhs)); +} + +/*! @copydoc operator| */ +template +constexpr std::enable_if_t, Type &> +operator&=(Type &lhs, const Type rhs) ENTT_NOEXCEPT { + return (lhs = (lhs & rhs)); +} + +/*! @copydoc operator| */ +template +constexpr std::enable_if_t, Type &> +operator^=(Type &lhs, const Type rhs) ENTT_NOEXCEPT { + return (lhs = (lhs ^ rhs)); +} + +#endif diff --git a/modules/entt/src/entt/core/family.hpp b/modules/entt/src/entt/core/family.hpp index 138ec05..4df1ec0 100644 --- a/modules/entt/src/entt/core/family.hpp +++ b/modules/entt/src/entt/core/family.hpp @@ -1,14 +1,11 @@ #ifndef ENTT_CORE_FAMILY_HPP #define ENTT_CORE_FAMILY_HPP - -#include #include "../config/config.h" - +#include "fwd.hpp" namespace entt { - /** * @brief Dynamic identifier generator. * @@ -18,23 +15,18 @@ namespace entt { */ template class family { - inline static maybe_atomic_t identifier; - - template - inline static const auto inner = identifier++; + inline static ENTT_MAYBE_ATOMIC(id_type) identifier{}; public: /*! @brief Unsigned integer type. */ - using family_type = ENTT_ID_TYPE; + using family_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 in place of family_type here - inline static const family_type type = inner...>; + // at the time I'm writing, clang crashes during compilation if auto is used instead of family_type + inline static const family_type type = identifier++; }; +} // namespace entt -} - - -#endif // ENTT_CORE_FAMILY_HPP +#endif diff --git a/modules/entt/src/entt/core/fwd.hpp b/modules/entt/src/entt/core/fwd.hpp new file mode 100644 index 0000000..cec32a0 --- /dev/null +++ b/modules/entt/src/entt/core/fwd.hpp @@ -0,0 +1,21 @@ +#ifndef ENTT_CORE_FWD_HPP +#define ENTT_CORE_FWD_HPP + +#include +#include +#include "../config/config.h" + +namespace entt { + +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<>; + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/core/hashed_string.hpp b/modules/entt/src/entt/core/hashed_string.hpp index 1a711ea..f18de66 100644 --- a/modules/entt/src/entt/core/hashed_string.hpp +++ b/modules/entt/src/entt/core/hashed_string.hpp @@ -1,213 +1,333 @@ #ifndef ENTT_CORE_HASHED_STRING_HPP #define ENTT_CORE_HASHED_STRING_HPP - #include +#include #include "../config/config.h" - +#include "fwd.hpp" namespace entt { - /** * @cond TURN_OFF_DOXYGEN * Internal details not to be documented. */ - namespace internal { - template struct fnv1a_traits; - template<> struct fnv1a_traits { + using type = std::uint32_t; static constexpr std::uint32_t offset = 2166136261; static constexpr std::uint32_t prime = 16777619; }; - template<> struct fnv1a_traits { + using type = std::uint64_t; static constexpr std::uint64_t offset = 14695981039346656037ull; static constexpr std::uint64_t prime = 1099511628211ull; }; +template +struct basic_hashed_string { + using value_type = Char; + using size_type = std::size_t; + using hash_type = id_type; -} + const value_type *repr; + size_type length; + hash_type hash; +}; +} // namespace internal /** * Internal details not to be documented. - * @endcond TURN_OFF_DOXYGEN + * @endcond */ - /** * @brief Zero overhead unique identifier. * * A hashed string is a compile-time tool that allows users to use - * human-readable identifers in the codebase while using their numeric + * 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. */ -class hashed_string { - using traits_type = internal::fnv1a_traits; +template +class basic_hashed_string: internal::basic_hashed_string { + using base_type = internal::basic_hashed_string; + using hs_traits = internal::fnv1a_traits; struct const_wrapper { // non-explicit constructor on purpose - constexpr const_wrapper(const char *curr) ENTT_NOEXCEPT: str{curr} {} - const char *str; + constexpr const_wrapper(const Char *str) ENTT_NOEXCEPT: repr{str} {} + const Char *repr; }; // Fowler–Noll–Vo hash function v. 1a - the good - inline static constexpr ENTT_ID_TYPE helper(ENTT_ID_TYPE partial, const char *curr) ENTT_NOEXCEPT { - return curr[0] == 0 ? partial : helper((partial^curr[0])*traits_type::prime, curr+1); + [[nodiscard]] static constexpr auto helper(const Char *str) ENTT_NOEXCEPT { + base_type base{str, 0u, hs_traits::offset}; + + for(; str[base.length]; ++base.length) { + base.hash = (base.hash ^ static_cast(str[base.length])) * hs_traits::prime; + } + + return base; + } + + // Fowler–Noll–Vo hash function v. 1a - the good + [[nodiscard]] static constexpr auto helper(const Char *str, const std::size_t len) ENTT_NOEXCEPT { + base_type base{str, len, hs_traits::offset}; + + for(size_type pos{}; pos < len; ++pos) { + base.hash = (base.hash ^ static_cast(str[pos])) * hs_traits::prime; + } + + return base; } public: + /*! @brief Character type. */ + using value_type = typename base_type::value_type; + /*! @brief Unsigned integer type. */ + using size_type = typename base_type::size_type; /*! @brief Unsigned integer type. */ - using hash_type = ENTT_ID_TYPE; + using hash_type = typename base_type::hash_type; /** - * @brief Returns directly the numeric representation of a string. - * - * Forcing template resolution avoids implicit conversions. An - * human-readable identifier can be anything but a plain, old bunch of - * characters.
- * Example of use: - * @code{.cpp} - * const auto value = hashed_string::to_value("my.png"); - * @endcode - * - * @tparam N Number of characters of the identifier. - * @param str Human-readable identifer. + * @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. */ - template - inline static constexpr hash_type to_value(const char (&str)[N]) ENTT_NOEXCEPT { - return helper(traits_type::offset, str); + [[nodiscard]] static constexpr hash_type value(const value_type *str, const size_type len) ENTT_NOEXCEPT { + return basic_hashed_string{str, len}; } /** * @brief Returns directly the numeric representation of a string. - * @param wrapper Helps achieving the purpose by relying on overloading. + * @tparam N Number of characters of the identifier. + * @param str Human-readable identifier. * @return The numeric representation of the string. */ - inline static hash_type to_value(const_wrapper wrapper) ENTT_NOEXCEPT { - return helper(traits_type::offset, wrapper.str); + template + [[nodiscard]] static constexpr hash_type value(const value_type (&str)[N]) ENTT_NOEXCEPT { + return basic_hashed_string{str}; } /** - * @brief Returns directly the numeric representation of a string view. - * @param str Human-readable identifer. - * @param size Length of the string to hash. + * @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. */ - inline static hash_type to_value(const char *str, std::size_t size) ENTT_NOEXCEPT { - ENTT_ID_TYPE partial{traits_type::offset}; - while(size--) { partial = (partial^(str++)[0])*traits_type::prime; } - return partial; + [[nodiscard]] static constexpr hash_type value(const_wrapper wrapper) ENTT_NOEXCEPT { + return basic_hashed_string{wrapper}; } /*! @brief Constructs an empty hashed string. */ - constexpr hashed_string() ENTT_NOEXCEPT - : str{nullptr}, hash{} - {} + constexpr basic_hashed_string() ENTT_NOEXCEPT + : base_type{} {} /** - * @brief Constructs a hashed string from an array of const chars. - * - * Forcing template resolution avoids implicit conversions. An - * human-readable identifier can be anything but a plain, old bunch of - * characters.
- * Example of use: - * @code{.cpp} - * hashed_string hs{"my.png"}; - * @endcode - * + * @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) ENTT_NOEXCEPT + : base_type{helper(str, len)} {} + + /** + * @brief Constructs a hashed string from an array of const characters. * @tparam N Number of characters of the identifier. - * @param curr Human-readable identifer. + * @param str Human-readable identifier. */ template - constexpr hashed_string(const char (&curr)[N]) ENTT_NOEXCEPT - : str{curr}, hash{helper(traits_type::offset, curr)} - {} + constexpr basic_hashed_string(const value_type (&str)[N]) ENTT_NOEXCEPT + : base_type{helper(str)} {} /** * @brief Explicit constructor on purpose to avoid constructing a hashed - * string directly from a `const char *`. + * 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 hashed_string(const_wrapper wrapper) ENTT_NOEXCEPT - : str{wrapper.str}, hash{helper(traits_type::offset, wrapper.str)} - {} + explicit constexpr basic_hashed_string(const_wrapper wrapper) ENTT_NOEXCEPT + : base_type{helper(wrapper.repr)} {} /** - * @brief Returns the human-readable representation of a hashed string. - * @return The string used to initialize the instance. + * @brief Returns the size a hashed string. + * @return The size of the hashed string. */ - constexpr const char * data() const ENTT_NOEXCEPT { - return str; + [[nodiscard]] constexpr size_type size() const ENTT_NOEXCEPT { + return base_type::length; } /** - * @brief Returns the numeric representation of a hashed string. - * @return The numeric representation of the instance. + * @brief Returns the human-readable representation of a hashed string. + * @return The string used to initialize the hashed string. */ - constexpr hash_type value() const ENTT_NOEXCEPT { - return hash; + [[nodiscard]] constexpr const value_type *data() const ENTT_NOEXCEPT { + return base_type::repr; } /** - * @brief Returns the human-readable representation of a hashed string. - * @return The string used to initialize the instance. + * @brief Returns the numeric representation of a hashed string. + * @return The numeric representation of the hashed string. */ - constexpr operator const char *() const ENTT_NOEXCEPT { return str; } + [[nodiscard]] constexpr hash_type value() const ENTT_NOEXCEPT { + return base_type::hash; + } - /*! @copydoc value */ - constexpr operator hash_type() const ENTT_NOEXCEPT { return hash; } + /*! @copydoc data */ + [[nodiscard]] constexpr operator const value_type *() const ENTT_NOEXCEPT { + return data(); + } /** - * @brief Compares two hashed strings. - * @param other Hashed string with which to compare. - * @return True if the two hashed strings are identical, false otherwise. + * @brief Returns the numeric representation of a hashed string. + * @return The numeric representation of the hashed string. */ - constexpr bool operator==(const hashed_string &other) const ENTT_NOEXCEPT { - return hash == other.hash; + [[nodiscard]] constexpr operator hash_type() const ENTT_NOEXCEPT { + return value(); } - -private: - const char *str; - hash_type hash; }; +/** + * @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, const std::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 +basic_hashed_string(const Char (&str)[N]) -> basic_hashed_string; /** * @brief Compares two hashed strings. + * @tparam Char Character type. * @param lhs A valid hashed string. * @param rhs A valid hashed string. * @return True if the two hashed strings are identical, false otherwise. */ -constexpr bool operator!=(const hashed_string &lhs, const hashed_string &rhs) ENTT_NOEXCEPT { +template +[[nodiscard]] constexpr bool operator==(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return lhs.value() == rhs.value(); +} + +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the two hashed strings differ, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator!=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { return !(lhs == rhs); } +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is less than the second, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator<(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return lhs.value() < rhs.value(); +} +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is less than or equal to the second, false + * otherwise. + */ +template +[[nodiscard]] constexpr bool operator<=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return !(rhs < lhs); } +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is greater than the second, false + * otherwise. + */ +template +[[nodiscard]] constexpr bool operator>(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +/** + * @brief Compares two hashed strings. + * @tparam Char Character type. + * @param lhs A valid hashed string. + * @param rhs A valid hashed string. + * @return True if the first element is greater than or equal to the second, + * false otherwise. + */ +template +[[nodiscard]] constexpr bool operator>=(const basic_hashed_string &lhs, const basic_hashed_string &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +/*! @brief Aliases for common character types. */ +using hashed_string = basic_hashed_string; + +/*! @brief Aliases for common character types. */ +using hashed_wstring = 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. */ -constexpr entt::hashed_string operator"" ENTT_HS_SUFFIX(const char *str, std::size_t) ENTT_NOEXCEPT { - return entt::hashed_string{str}; +[[nodiscard]] constexpr hashed_string operator"" _hs(const char *str, std::size_t) ENTT_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]] constexpr hashed_wstring operator"" _hws(const wchar_t *str, std::size_t) ENTT_NOEXCEPT { + return hashed_wstring{str}; +} + +} // namespace literals + +} // namespace entt -#endif // ENTT_CORE_HASHED_STRING_HPP +#endif diff --git a/modules/entt/src/entt/core/ident.hpp b/modules/entt/src/entt/core/ident.hpp index c64d097..b64c45e 100644 --- a/modules/entt/src/entt/core/ident.hpp +++ b/modules/entt/src/entt/core/ident.hpp @@ -1,16 +1,15 @@ #ifndef ENTT_CORE_IDENT_HPP #define ENTT_CORE_IDENT_HPP - -#include -#include +#include #include +#include #include "../config/config.h" - +#include "fwd.hpp" +#include "type_traits.hpp" namespace entt { - /** * @brief Types identifiers. * @@ -40,25 +39,21 @@ namespace entt { */ template class identifier { - using tuple_type = std::tuple...>; - - template - static constexpr ENTT_ID_TYPE get(std::index_sequence) ENTT_NOEXCEPT { - static_assert(std::disjunction_v...>); - return (0 + ... + (std::is_same_v> ? ENTT_ID_TYPE(Indexes) : ENTT_ID_TYPE{})); + template + [[nodiscard]] static constexpr id_type get(std::index_sequence) ENTT_NOEXCEPT { + static_assert((std::is_same_v || ...), "Invalid type"); + return (0 + ... + (std::is_same_v...>>> ? id_type{Index} : id_type{})); } public: /*! @brief Unsigned integer type. */ - using identifier_type = ENTT_ID_TYPE; + using identifier_type = id_type; /*! @brief Statically generated unique identifier for the given type. */ template - static constexpr identifier_type type = get>(std::make_index_sequence{}); + static constexpr identifier_type type = get>(std::index_sequence_for{}); }; +} // namespace entt -} - - -#endif // ENTT_CORE_IDENT_HPP +#endif diff --git a/modules/entt/src/entt/core/iterator.hpp b/modules/entt/src/entt/core/iterator.hpp new file mode 100644 index 0000000..2849cd8 --- /dev/null +++ b/modules/entt/src/entt/core/iterator.hpp @@ -0,0 +1,117 @@ +#ifndef ENTT_CORE_ITERATOR_HPP +#define ENTT_CORE_ITERATOR_HPP + +#include +#include +#include +#include "../config/config.h" + +namespace entt { + +/** + * @brief Helper type to use as pointer with input iterators. + * @tparam Type of wrapped value. + */ +template +struct input_iterator_pointer final { + /*! @brief Pointer type. */ + using pointer = Type *; + + /*! @brief Default copy constructor, deleted on purpose. */ + input_iterator_pointer(const input_iterator_pointer &) = delete; + + /*! @brief Default move constructor. */ + input_iterator_pointer(input_iterator_pointer &&) = default; + + /** + * @brief Constructs a proxy object by move. + * @param val Value to use to initialize the proxy object. + */ + input_iterator_pointer(Type &&val) + : value{std::move(val)} {} + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This proxy object. + */ + input_iterator_pointer &operator=(const input_iterator_pointer &) = delete; + + /** + * @brief Default move assignment operator. + * @return This proxy object. + */ + input_iterator_pointer &operator=(input_iterator_pointer &&) = default; + + /** + * @brief Access operator for accessing wrapped values. + * @return A pointer to the wrapped value. + */ + [[nodiscard]] pointer operator->() ENTT_NOEXCEPT { + return std::addressof(value); + } + +private: + Type value; +}; + +/** + * @brief Utility class to create an iterable object from a pair of iterators. + * @tparam It Type of iterator. + * @tparam Sentinel Type of sentinel. + */ +template +struct iterable_adaptor final { + /*! @brief Value type. */ + using value_type = typename std::iterator_traits::value_type; + /*! @brief Iterator type. */ + using iterator = It; + /*! @brief Sentinel type. */ + using sentinel = Sentinel; + + /*! @brief Default constructor. */ + iterable_adaptor() = default; + + /** + * @brief Creates an iterable object from a pair of iterators. + * @param from Begin iterator. + * @param to End iterator. + */ + iterable_adaptor(iterator from, sentinel to) + : first{from}, + last{to} {} + + /** + * @brief Returns an iterator to the beginning. + * @return An iterator to the first element of the range. + */ + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return first; + } + + /** + * @brief Returns an iterator to the end. + * @return An iterator to the element following the last element of the + * range. + */ + [[nodiscard]] sentinel end() const ENTT_NOEXCEPT { + return last; + } + + /*! @copydoc begin */ + [[nodiscard]] iterator cbegin() const ENTT_NOEXCEPT { + return begin(); + } + + /*! @copydoc end */ + [[nodiscard]] sentinel cend() const ENTT_NOEXCEPT { + return end(); + } + +private: + It first; + Sentinel last; +}; + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/core/memory.hpp b/modules/entt/src/entt/core/memory.hpp new file mode 100644 index 0000000..933f1ac --- /dev/null +++ b/modules/entt/src/entt/core/memory.hpp @@ -0,0 +1,289 @@ +#ifndef ENTT_CORE_MEMORY_HPP +#define ENTT_CORE_MEMORY_HPP + +#include +#include +#include +#include +#include +#include +#include "../config/config.h" + +namespace entt { + +/** + * @brief Unwraps fancy pointers, does nothing otherwise (waiting for C++20). + * @tparam Type Pointer type. + * @param ptr Fancy or raw pointer. + * @return A raw pointer that represents the address of the original pointer. + */ +template +[[nodiscard]] constexpr auto to_address(Type &&ptr) ENTT_NOEXCEPT { + if constexpr(std::is_pointer_v>>) { + return ptr; + } else { + return to_address(std::forward(ptr).operator->()); + } +} + +/** + * @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) ENTT_NOEXCEPT { + if constexpr(std::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) ENTT_NOEXCEPT { + if constexpr(std::allocator_traits::propagate_on_container_move_assignment::value) { + lhs = std::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) ENTT_NOEXCEPT { + ENTT_ASSERT(std::allocator_traits::propagate_on_container_swap::value || lhs == rhs, "Cannot swap the containers"); + + if constexpr(std::allocator_traits::propagate_on_container_swap::value) { + using std::swap; + swap(lhs, rhs); + } +} + +/** + * @brief Checks whether a value is a power of two or not. + * @param value A value that may or may not be a power of two. + * @return True if the value is a power of two, false otherwise. + */ +[[nodiscard]] inline constexpr bool is_power_of_two(const std::size_t value) ENTT_NOEXCEPT { + return value && ((value & (value - 1)) == 0); +} + +/** + * @brief Computes the smallest power of two greater than or equal to a value. + * @param value The value to use. + * @return The smallest power of two greater than or equal to the given value. + */ +[[nodiscard]] inline constexpr std::size_t next_power_of_two(const std::size_t value) ENTT_NOEXCEPT { + ENTT_ASSERT(value < (std::size_t{1u} << (std::numeric_limits::digits - 1)), "Numeric limits exceeded"); + std::size_t curr = value - (value != 0u); + + for(int next = 1; next < std::numeric_limits::digits; next = next * 2) { + curr |= curr >> next; + } + + return ++curr; +} + +/** + * @brief Fast module utility function (powers of two only). + * @param value A value for which to calculate the modulus. + * @param mod _Modulus_, it must be a power of two. + * @return The common remainder. + */ +[[nodiscard]] inline constexpr std::size_t fast_mod(const std::size_t value, const std::size_t mod) ENTT_NOEXCEPT { + ENTT_ASSERT(is_power_of_two(mod), "Value must be a power of two"); + return value & (mod - 1u); +} + +/** + * @brief Deleter for allocator-aware unique pointers (waiting for C++20). + * @tparam Args Types of arguments to use to construct the object. + */ +template +struct allocation_deleter: private Allocator { + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Pointer type. */ + using pointer = typename std::allocator_traits::pointer; + + /** + * @brief Inherited constructors. + * @param alloc The allocator to use. + */ + allocation_deleter(const allocator_type &alloc) + : Allocator{alloc} {} + + /** + * @brief Destroys the pointed object and deallocates its memory. + * @param ptr A valid pointer to an object of the given type. + */ + void operator()(pointer ptr) { + using alloc_traits = typename std::allocator_traits; + alloc_traits::destroy(*this, to_address(ptr)); + alloc_traits::deallocate(*this, ptr, 1u); + } +}; + +/** + * @brief Allows `std::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 +auto allocate_unique(Allocator &allocator, Args &&...args) { + static_assert(!std::is_array_v, "Array types are not supported"); + + using alloc_traits = typename std::allocator_traits::template rebind_traits; + using allocator_type = typename alloc_traits::allocator_type; + + allocator_type alloc{allocator}; + auto ptr = alloc_traits::allocate(alloc, 1u); + + ENTT_TRY { + alloc_traits::construct(alloc, to_address(ptr), std::forward(args)...); + } + ENTT_CATCH { + alloc_traits::deallocate(alloc, ptr, 1u); + ENTT_THROW; + } + + return std::unique_ptr>{ptr, alloc}; +} + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct uses_allocator_construction { + template + static constexpr auto args([[maybe_unused]] const Allocator &allocator, Params &&...params) ENTT_NOEXCEPT { + if constexpr(!std::uses_allocator_v && std::is_constructible_v) { + return std::forward_as_tuple(std::forward(params)...); + } else { + static_assert(std::uses_allocator_v, "Ill-formed request"); + + if constexpr(std::is_constructible_v) { + return std::tuple(std::allocator_arg, allocator, std::forward(params)...); + } else { + static_assert(std::is_constructible_v, "Ill-formed request"); + return std::forward_as_tuple(std::forward(params)..., allocator); + } + } + } +}; + +template +struct uses_allocator_construction> { + using type = std::pair; + + template + static constexpr auto args(const Allocator &allocator, std::piecewise_construct_t, First &&first, Second &&second) ENTT_NOEXCEPT { + return std::make_tuple( + std::piecewise_construct, + std::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, std::forward(curr)...); }, std::forward(first)), + std::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, std::forward(curr)...); }, std::forward(second))); + } + + template + static constexpr auto args(const Allocator &allocator) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::tuple<>{}, std::tuple<>{}); + } + + template + static constexpr auto args(const Allocator &allocator, First &&first, Second &&second) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::forward(first)), std::forward_as_tuple(std::forward(second))); + } + + template + static constexpr auto args(const Allocator &allocator, const std::pair &value) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(value.first), std::forward_as_tuple(value.second)); + } + + template + static constexpr auto args(const Allocator &allocator, std::pair &&value) ENTT_NOEXCEPT { + return uses_allocator_construction::args(allocator, std::piecewise_construct, std::forward_as_tuple(std::move(value.first)), std::forward_as_tuple(std::move(value.second))); + } +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @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 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 The arguments needed to create an object of the given type. + */ +template +constexpr auto uses_allocator_construction_args(const Allocator &allocator, Args &&...args) ENTT_NOEXCEPT { + return internal::uses_allocator_construction::args(allocator, std::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 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 newly created object of the given type. + */ +template +constexpr Type make_obj_using_allocator(const Allocator &allocator, Args &&...args) { + return std::make_from_tuple(internal::uses_allocator_construction::args(allocator, std::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 Allocator Type of allocator used to manage memory and elements. + * @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 Allocator &allocator, Args &&...args) { + return std::apply([&](auto &&...curr) { return new(value) Type(std::forward(curr)...); }, internal::uses_allocator_construction::args(allocator, std::forward(args)...)); +} + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/core/monostate.hpp b/modules/entt/src/entt/core/monostate.hpp index fbca09a..1b08a0a 100644 --- a/modules/entt/src/entt/core/monostate.hpp +++ b/modules/entt/src/entt/core/monostate.hpp @@ -1,15 +1,11 @@ #ifndef ENTT_CORE_MONOSTATE_HPP #define ENTT_CORE_MONOSTATE_HPP - -#include #include "../config/config.h" -#include "hashed_string.hpp" - +#include "fwd.hpp" namespace entt { - /** * @brief Minimal implementation of the monostate pattern. * @@ -21,7 +17,7 @@ namespace entt { * both during an assignment and when they try to read back their data. * Otherwise, they can incur in unexpected results. */ -template +template struct monostate { /** * @brief Assigns a value of a specific type to a given key. @@ -45,19 +41,16 @@ struct monostate { private: template - inline static maybe_atomic_t value{}; + inline static ENTT_MAYBE_ATOMIC(Type) value{}; }; - /** * @brief Helper variable template. * @tparam Value Value used to differentiate between different variables. */ -template +template inline monostate monostate_v = {}; +} // namespace entt -} - - -#endif // ENTT_CORE_MONOSTATE_HPP +#endif diff --git a/modules/entt/src/entt/core/tuple.hpp b/modules/entt/src/entt/core/tuple.hpp new file mode 100644 index 0000000..4338b29 --- /dev/null +++ b/modules/entt/src/entt/core/tuple.hpp @@ -0,0 +1,29 @@ +#ifndef ENTT_CORE_TUPLE_HPP +#define ENTT_CORE_TUPLE_HPP + +#include +#include +#include +#include "../config/config.h" + +namespace entt { + +/** + * @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) ENTT_NOEXCEPT { + if constexpr(std::tuple_size_v> == 1u) { + return std::get<0>(std::forward(value)); + } else { + return std::forward(value); + } +} + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/core/type_info.hpp b/modules/entt/src/entt/core/type_info.hpp new file mode 100644 index 0000000..bcae911 --- /dev/null +++ b/modules/entt/src/entt/core/type_info.hpp @@ -0,0 +1,274 @@ +#ifndef ENTT_CORE_TYPE_INFO_HPP +#define ENTT_CORE_TYPE_INFO_HPP + +#include +#include +#include +#include "../config/config.h" +#include "../core/attribute.h" +#include "fwd.hpp" +#include "hashed_string.hpp" + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +struct ENTT_API type_index final { + [[nodiscard]] static id_type next() ENTT_NOEXCEPT { + static ENTT_MAYBE_ATOMIC(id_type) value{}; + return value++; + } +}; + +template +[[nodiscard]] constexpr auto stripped_type_name() ENTT_NOEXCEPT { +#if defined ENTT_PRETTY_FUNCTION + std::string_view pretty_function{ENTT_PRETTY_FUNCTION}; + auto first = pretty_function.find_first_not_of(' ', pretty_function.find_first_of(ENTT_PRETTY_FUNCTION_PREFIX) + 1); + auto value = pretty_function.substr(first, pretty_function.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first); + return value; +#else + return std::string_view{""}; +#endif +} + +template().find_first_of('.')> +[[nodiscard]] static constexpr std::string_view type_name(int) ENTT_NOEXCEPT { + constexpr auto value = stripped_type_name(); + return value; +} + +template +[[nodiscard]] static std::string_view type_name(char) ENTT_NOEXCEPT { + static const auto value = stripped_type_name(); + return value; +} + +template().find_first_of('.')> +[[nodiscard]] static constexpr id_type type_hash(int) ENTT_NOEXCEPT { + constexpr auto stripped = stripped_type_name(); + constexpr auto value = hashed_string::value(stripped.data(), stripped.size()); + return value; +} + +template +[[nodiscard]] static id_type type_hash(char) ENTT_NOEXCEPT { + static const auto value = [](const auto stripped) { + return hashed_string::value(stripped.data(), stripped.size()); + }(stripped_type_name()); + return value; +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @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() ENTT_NOEXCEPT { + static const id_type value = internal::type_index::next(); + return value; + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const ENTT_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() ENTT_NOEXCEPT { + return internal::type_hash(0); +#else + [[nodiscard]] static constexpr id_type value() ENTT_NOEXCEPT { + return type_index::value(); +#endif + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator id_type() const ENTT_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 std::string_view value() ENTT_NOEXCEPT { + return internal::type_name(0); + } + + /*! @copydoc value */ + [[nodiscard]] constexpr operator std::string_view() const ENTT_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 + constexpr type_info(std::in_place_type_t) ENTT_NOEXCEPT + : seq{type_index>>::value()}, + identifier{type_hash>>::value()}, + alias{type_name>>::value()} {} + + /** + * @brief Type index. + * @return Type index. + */ + [[nodiscard]] constexpr id_type index() const ENTT_NOEXCEPT { + return seq; + } + + /** + * @brief Type hash. + * @return Type hash. + */ + [[nodiscard]] constexpr id_type hash() const ENTT_NOEXCEPT { + return identifier; + } + + /** + * @brief Type name. + * @return Type name. + */ + [[nodiscard]] constexpr std::string_view name() const ENTT_NOEXCEPT { + return alias; + } + +private: + id_type seq; + id_type identifier; + std::string_view alias; +}; + +/** + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects are identical, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator==(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return lhs.hash() == rhs.hash(); +} + +/** + * @brief Compares the contents of two type info objects. + * @param lhs A type info object. + * @param rhs A type info object. + * @return True if the two type info objects differ, false otherwise. + */ +[[nodiscard]] inline constexpr bool operator!=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than the second, false otherwise. + */ +[[nodiscard]] constexpr bool operator<(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return lhs.index() < rhs.index(); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is less than or equal to the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator<=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(rhs < lhs); +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than the second, false + * otherwise. + */ +[[nodiscard]] constexpr bool operator>(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +/** + * @brief Compares two type info objects. + * @param lhs A valid type info object. + * @param rhs A valid type info object. + * @return True if the first element is greater than or equal to the second, + * false otherwise. + */ +[[nodiscard]] constexpr bool operator>=(const type_info &lhs, const type_info &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +/** + * @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() ENTT_NOEXCEPT { + if constexpr(std::is_same_v>>) { + static type_info instance{std::in_place_type}; + return instance; + } else { + return type_id>>(); + } +} + +/*! @copydoc type_id */ +template +[[nodiscard]] const type_info &type_id(Type &&) ENTT_NOEXCEPT { + return type_id>>(); +} + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/core/type_traits.hpp b/modules/entt/src/entt/core/type_traits.hpp index 9b53bd7..e6ea738 100644 --- a/modules/entt/src/entt/core/type_traits.hpp +++ b/modules/entt/src/entt/core/type_traits.hpp @@ -1,30 +1,167 @@ #ifndef ENTT_CORE_TYPE_TRAITS_HPP #define ENTT_CORE_TYPE_TRAITS_HPP - +#include +#include #include -#include "../core/hashed_string.hpp" - +#include +#include "../config/config.h" +#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 TURN_OFF_DOXYGEN */ 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 Identity type trait. + * + * Useful to establish non-deduced contexts in template argument deduction + * (waiting for C++20) or to provide types through function arguments. + * + * @tparam Type A type. + */ +template +struct type_identity { + /*! @brief Identity type. */ + using type = Type; +}; + +/** + * @brief Helper type. + * @tparam Type A type. + */ +template +using type_identity_t = typename type_identity::type; + +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + * @tparam The size of the type if `sizeof` accepts it, 0 otherwise. + */ +template +struct size_of: std::integral_constant {}; + +/*! @copydoc size_of */ +template +struct size_of> + : std::integral_constant {}; + +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr std::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 = std::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 given type list. + * @tparam Type Types provided by the type list. */ template struct type_list { - /*! @brief Unsigned integer type. */ + /*! @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 Type 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 Type 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 = Type; +}; + +/** + * @brief Helper type. + * @tparam Index Index of the type to return. + * @tparam List Type list to search into. + */ +template +using type_list_element_t = typename type_list_element::type; + +/** + * @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 +constexpr 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<> { @@ -32,7 +169,6 @@ struct type_list_cat<> { using type = type_list<>; }; - /** * @brief Concatenates multiple type lists. * @tparam Type Types provided by the first type list. @@ -45,7 +181,6 @@ struct type_list_cat, type_list, List...> { using type = typename type_list_cat, List...>::type; }; - /** * @brief Concatenates multiple type lists. * @tparam Type Types provided by the type list. @@ -56,7 +191,6 @@ struct type_list_cat> { using type = type_list; }; - /** * @brief Helper type. * @tparam List Type lists to concatenate. @@ -64,12 +198,10 @@ struct type_list_cat> { template using type_list_cat_t = typename type_list_cat::type; - /*! @brief Primary template isn't defined on purpose. */ template struct type_list_unique; - /** * @brief Removes duplicates types from a type list. * @tparam Type One of the types provided by the given type list. @@ -79,13 +211,11 @@ template struct type_list_unique> { /*! @brief A type list without duplicate types. */ using type = std::conditional_t< - std::disjunction_v...>, + (std::is_same_v || ...), typename type_list_unique>::type, - type_list_cat_t, typename type_list_unique>::type> - >; + type_list_cat_t, typename type_list_unique>::type>>; }; - /*! @brief Removes duplicates types from a type list. */ template<> struct type_list_unique> { @@ -93,7 +223,6 @@ struct type_list_unique> { using type = type_list<>; }; - /** * @brief Helper type. * @tparam Type A type list. @@ -101,136 +230,422 @@ struct type_list_unique> { template using type_list_unique_t = typename type_list_unique::type; +/** + * @brief Provides the member constant `value` 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; -/*! @brief Traits class used mainly to push things across boundaries. */ -template -struct named_type_traits; +/** + * @copybrief type_list_contains + * @tparam Type Types provided by the type list. + * @tparam Other Type to look for. + */ +template +struct type_list_contains, Other>: std::disjunction...> {}; + +/** + * @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 Specialization used to get rid of constness. - * @tparam Type Named type. + * @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 named_type_traits - : named_type_traits -{}; +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 = typename type_list_diff::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 value. */ + static constexpr auto value = Value; +}; /** * @brief Helper type. - * @tparam Type Potentially named type. + * @tparam Index Index of the value to return. + * @tparam List Value list to search into. */ -template -using named_type_traits_t = typename named_type_traits::type; +template +inline constexpr auto value_list_element_v = value_list_element::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 +constexpr 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 Provides the member constant `value` to true if a given type has a - * name. In all other cases, `value` is false. + * @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 is_named_type: std::false_type {}; +template +struct value_list_cat, value_list, List...> { + /*! @brief A value list composed by the values of all the value lists. */ + using type = typename 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 Provides the member constant `value` to true if a given type has a - * name. In all other cases, `value` is false. - * @tparam Type Potentially named type. + * @brief Helper type. + * @tparam List Value lists to concatenate. */ -template -struct is_named_type>>>: std::true_type {}; +template +using value_list_cat_t = typename value_list_cat::type; +/*! @brief Same as std::is_invocable, but with tuples. */ +template +struct is_applicable: std::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>: std::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>: std::is_invocable {}; /** * @brief Helper variable template. - * - * True if a given type has a name, false otherwise. - * - * @tparam Type Potentially named type. + * @tparam Func A valid function type. + * @tparam Args The list of arguments to use to probe the function type. */ -template -constexpr auto is_named_type_v = is_named_type::value; +template +inline constexpr bool is_applicable_v = is_applicable::value; +/*! @brief Same as std::is_invocable_r, but with tuples for arguments. */ +template +struct is_applicable_r: std::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>: std::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 Utility macro to deal with an issue of MSVC. - * - * See _msvc-doesnt-expand-va-args-correctly_ on SO for all the details. - * - * @param args Argument to expand. + * @brief Provides the member constant `value` to true if a given type is + * complete, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_complete: std::false_type {}; + +/*! @copydoc is_complete */ +template +struct is_complete>: std::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` to true if a given type is an + * iterator, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_iterator: std::false_type {}; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct has_iterator_category: std::false_type {}; + +template +struct has_iterator_category::iterator_category>>: std::true_type {}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @copydoc is_iterator */ +template +struct is_iterator>, void>>> + : internal::has_iterator_category {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. */ -#define ENTT_EXPAND(args) args +template +inline constexpr bool is_iterator_v = is_iterator::value; +/** + * @brief Provides the member constant `value` 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 + : std::conjunction, std::negation>> {}; /** - * @brief Makes an already existing type a named type. - * @param type Type to assign a name to. + * @brief Helper variable template. + * @tparam Type The type to test. */ -#define ENTT_NAMED_TYPE(type)\ - template<>\ - struct entt::named_type_traits\ - : std::integral_constant\ - {\ - static_assert(std::is_same_v, type>);\ - }; +template +inline constexpr bool is_ebco_eligible_v = is_ebco_eligible::value; +/** + * @brief Provides the member constant `value` to true if `Type::is_transparent` + * is valid and denotes a type, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_transparent: std::false_type {}; + +/*! @copydoc is_transparent */ +template +struct is_transparent>: std::true_type {}; /** - * @brief Defines a named type (to use for structs). - * @param clazz Name of the type to define. - * @param body Body of the type to define. + * @brief Helper variable template. + * @tparam Type The type to test. */ -#define ENTT_NAMED_STRUCT_ONLY(clazz, body)\ - struct clazz body;\ - ENTT_NAMED_TYPE(clazz) +template +inline constexpr bool is_transparent_v = is_transparent::value; +/** + * @brief Provides the member constant `value` to true if a given type is + * equality comparable, false otherwise. + * @tparam Type The type to test. + */ +template +struct is_equality_comparable: std::false_type {}; /** - * @brief Defines a named type (to use for structs). - * @param ns Namespace where to define the named type. - * @param clazz Name of the type to define. - * @param body Body of the type to define. + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. */ -#define ENTT_NAMED_STRUCT_WITH_NAMESPACE(ns, clazz, body)\ - namespace ns { struct clazz body; }\ - ENTT_NAMED_TYPE(ns::clazz) +namespace internal { + +template +struct has_tuple_size_value: std::false_type {}; + +template +struct has_tuple_size_value::value)>>: std::true_type {}; + +template +[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence) { + return (is_equality_comparable>::value && ...); +} + +template +[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) { + return true; +} + +template +[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval(), bool{}) { + if constexpr(is_iterator_v) { + return true; + } else if constexpr(std::is_same_v) { + return maybe_equality_comparable(choice<0>); + } else { + return is_equality_comparable::value; + } +} -/*! @brief Utility function to simulate macro overloading. */ -#define ENTT_NAMED_STRUCT_OVERLOAD(_1, _2, _3, FUNC, ...) FUNC -/*! @brief Defines a named type (to use for structs). */ -#define ENTT_NAMED_STRUCT(...) ENTT_EXPAND(ENTT_NAMED_STRUCT_OVERLOAD(__VA_ARGS__, ENTT_NAMED_STRUCT_WITH_NAMESPACE, ENTT_NAMED_STRUCT_ONLY,)(__VA_ARGS__)) +template +[[nodiscard]] constexpr std::enable_if_t>>, bool> maybe_equality_comparable(choice_t<2>) { + if constexpr(has_tuple_size_value::value) { + return unpack_maybe_equality_comparable(std::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(choice<1>); + } +} +} // namespace internal /** - * @brief Defines a named type (to use for classes). - * @param clazz Name of the type to define. - * @param body Body of the type to define. + * Internal details not to be documented. + * @endcond */ -#define ENTT_NAMED_CLASS_ONLY(clazz, body)\ - class clazz body;\ - ENTT_NAMED_TYPE(clazz) +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable() == std::declval())>> + : std::bool_constant(choice<2>)> {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_equality_comparable_v = is_equality_comparable::value; /** - * @brief Defines a named type (to use for classes). - * @param ns Namespace where to define the named type. - * @param clazz Name of the type to define. - * @param body Body of the type to define. + * @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. */ -#define ENTT_NAMED_CLASS_WITH_NAMESPACE(ns, clazz, body)\ - namespace ns { class clazz body; }\ - ENTT_NAMED_TYPE(ns::clazz) +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::remove_const_t; +}; +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = std::add_const_t; +}; -/*! @brief Utility function to simulate macro overloading. */ -#define ENTT_NAMED_CLASS_MACRO(_1, _2, _3, FUNC, ...) FUNC -/*! @brief Defines a named type (to use for classes). */ -#define ENTT_NAMED_CLASS(...) ENTT_EXPAND(ENTT_NAMED_CLASS_MACRO(__VA_ARGS__, ENTT_NAMED_CLASS_WITH_NAMESPACE, ENTT_NAMED_CLASS_ONLY,)(__VA_ARGS__)) +/** + * @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 = typename constness_as::type; + +/** + * @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 +class member_class { + static_assert(std::is_member_pointer_v, "Invalid pointer type to non-static member object or function"); + + 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 = std::remove_pointer_t()))>; +}; + +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = typename member_class::type; +} // namespace entt -#endif // ENTT_CORE_TYPE_TRAITS_HPP +#endif diff --git a/modules/entt/src/entt/core/utility.hpp b/modules/entt/src/entt/core/utility.hpp index e9006fc..f8a7240 100644 --- a/modules/entt/src/entt/core/utility.hpp +++ b/modules/entt/src/entt/core/utility.hpp @@ -1,32 +1,101 @@ #ifndef ENTT_CORE_UTILITY_HPP #define ENTT_CORE_UTILITY_HPP +#include +#include "../config/config.h" namespace entt { +/*! @brief Identity function object (waiting for C++20). */ +struct identity { + /*! @brief Indicates that this is a transparent function object. */ + using is_transparent = void; + + /** + * @brief Returns its argument unchanged. + * @tparam Type Type of the argument. + * @param value The actual argument. + * @return The submitted value as-is. + */ + template + [[nodiscard]] constexpr Type &&operator()(Type &&value) const ENTT_NOEXCEPT { + return std::forward(value); + } +}; /** - * @brief Constant utility to disambiguate overloaded member functions. - * @tparam Type Function type of the desired overload. - * @tparam Class Type of class to which the member functions belong. - * @param member A valid pointer to a member function. - * @return Pointer to the member function. + * @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 -constexpr auto overload(Type Class:: *member) { return member; } - +[[nodiscard]] constexpr auto overload(Type Class::*member) ENTT_NOEXCEPT { + return member; +} /** * @brief Constant utility to disambiguate overloaded functions. - * @tparam Type Function type of the desired overload. + * @tparam Func Function type of the desired overload. * @param func A valid pointer to a function. * @return Pointer to the function. */ -template -constexpr auto overload(Type *func) { return func; } +template +[[nodiscard]] constexpr auto overload(Func *func) ENTT_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. + */ + y_combinator(Func recursive) + : func{std::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 + decltype(auto) operator()(Args &&...args) const { + return func(*this, std::forward(args)...); + } + + /*! @copydoc operator()() */ + template + decltype(auto) operator()(Args &&...args) { + return func(*this, std::forward(args)...); + } + +private: + Func func; +}; +} // namespace entt -#endif // ENTT_CORE_UTILITY_HPP +#endif diff --git a/modules/entt/src/entt/entity/actor.hpp b/modules/entt/src/entt/entity/actor.hpp deleted file mode 100644 index 6340eb6..0000000 --- a/modules/entt/src/entt/entity/actor.hpp +++ /dev/null @@ -1,180 +0,0 @@ -#ifndef ENTT_ENTITY_ACTOR_HPP -#define ENTT_ENTITY_ACTOR_HPP - - -#include -#include -#include -#include "../config/config.h" -#include "registry.hpp" -#include "entity.hpp" -#include "fwd.hpp" - - -namespace entt { - - -/** - * @brief Dedicated to those who aren't confident with entity-component systems. - * - * Tiny wrapper around a registry, for all those users that aren't confident - * with entity-component systems and prefer to iterate objects directly. - * - * @tparam Entity A valid entity type (see entt_traits for more details). - */ -template -struct basic_actor { - /*! @brief Type of registry used internally. */ - using registry_type = basic_registry; - /*! @brief Underlying entity identifier. */ - using entity_type = Entity; - - /** - * @brief Constructs an actor by using the given registry. - * @param ref An entity-component system properly initialized. - */ - basic_actor(registry_type &ref) - : reg{&ref}, entt{ref.create()} - {} - - /*! @brief Default destructor. */ - virtual ~basic_actor() { - reg->destroy(entt); - } - - /** - * @brief Move constructor. - * - * After actor move construction, instances that have been moved from are - * placed in a valid but unspecified state. It's highly discouraged to - * continue using them. - * - * @param other The instance to move from. - */ - basic_actor(basic_actor &&other) - : reg{other.reg}, entt{other.entt} - { - other.entt = null; - } - - /** - * @brief Move assignment operator. - * - * After actor move assignment, instances that have been moved from are - * placed in a valid but unspecified state. It's highly discouraged to - * continue using them. - * - * @param other The instance to move from. - * @return This actor. - */ - basic_actor & operator=(basic_actor &&other) { - if(this != &other) { - auto tmp{std::move(other)}; - std::swap(reg, tmp.reg); - std::swap(entt, tmp.entt); - } - - return *this; - } - - /** - * @brief Assigns the given component to an actor. - * - * A new instance of the given component is created and initialized with the - * arguments provided (the component must have a proper constructor or be of - * aggregate type). Then the component is assigned to the actor.
- * In case the actor already has a component of the given type, it's - * replaced with the new one. - * - * @tparam Component Type of the component to create. - * @tparam Args Types of arguments to use to construct the component. - * @param args Parameters to use to initialize the component. - * @return A reference to the newly created component. - */ - template - decltype(auto) assign(Args &&... args) { - return reg->template assign_or_replace(entt, std::forward(args)...); - } - - /** - * @brief Removes the given component from an actor. - * @tparam Component Type of the component to remove. - */ - template - void remove() { - reg->template remove(entt); - } - - /** - * @brief Checks if an actor has the given component. - * @tparam Component Type of the component for which to perform the check. - * @return True if the actor has the component, false otherwise. - */ - template - bool has() const ENTT_NOEXCEPT { - return reg->template has(entt); - } - - /** - * @brief Returns references to the given components for an actor. - * @tparam Component Types of components to get. - * @return References to the components owned by the actor. - */ - template - decltype(auto) get() const ENTT_NOEXCEPT { - return std::as_const(*reg).template get(entt); - } - - /*! @copydoc get */ - template - decltype(auto) get() ENTT_NOEXCEPT { - return reg->template get(entt); - } - - /** - * @brief Returns pointers to the given components for an actor. - * @tparam Component Types of components to get. - * @return Pointers to the components owned by the actor. - */ - template - auto try_get() const ENTT_NOEXCEPT { - return std::as_const(*reg).template try_get(entt); - } - - /*! @copydoc try_get */ - template - auto try_get() ENTT_NOEXCEPT { - return reg->template try_get(entt); - } - - /** - * @brief Returns a reference to the underlying registry. - * @return A reference to the underlying registry. - */ - inline const registry_type & backend() const ENTT_NOEXCEPT { - return *reg; - } - - /*! @copydoc backend */ - inline registry_type & backend() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).backend()); - } - - /** - * @brief Returns the entity associated with an actor. - * @return The entity associated with the actor. - */ - inline entity_type entity() const ENTT_NOEXCEPT { - return entt; - } - -private: - registry_type *reg; - Entity entt; -}; - - -} - - -#endif // ENTT_ENTITY_ACTOR_HPP diff --git a/modules/entt/src/entt/entity/component.hpp b/modules/entt/src/entt/entity/component.hpp new file mode 100644 index 0000000..d604d21 --- /dev/null +++ b/modules/entt/src/entt/entity/component.hpp @@ -0,0 +1,61 @@ +#ifndef ENTT_ENTITY_COMPONENT_HPP +#define ENTT_ENTITY_COMPONENT_HPP + +#include +#include +#include "../config/config.h" + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct in_place_delete: std::false_type {}; + +template +struct in_place_delete> + : std::true_type {}; + +template +struct page_size: std::integral_constant) ? 0u : ENTT_PACKED_PAGE> {}; + +template +struct page_size>> + : std::integral_constant {}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Common way to access various properties of components. + * @tparam Type Type of component. + */ +template +struct component_traits { + static_assert(std::is_same_v, Type>, "Unsupported type"); + + /*! @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 std::size_t page_size = internal::page_size::value; +}; + +/** + * @brief Helper variable template. + * @tparam Type Type of component. + */ +template +inline constexpr bool ignore_as_empty_v = (component_traits::page_size == 0u); + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/entity/entity.hpp b/modules/entt/src/entt/entity/entity.hpp index 48123d6..330d057 100644 --- a/modules/entt/src/entt/entity/entity.hpp +++ b/modules/entt/src/entt/entity/entity.hpp @@ -1,169 +1,339 @@ #ifndef ENTT_ENTITY_ENTITY_HPP #define ENTT_ENTITY_ENTITY_HPP - +#include +#include +#include #include "../config/config.h" - +#include "fwd.hpp" namespace entt { - /** - * @brief Entity traits. - * - * Primary template isn't defined on purpose. All the specializations give a - * compile-time error unless the template parameter is an accepted entity type. + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. */ -template -struct entt_traits; +namespace internal { -/** - * @brief Entity traits for a 16 bits entity identifier. - * - * A 16 bits entity identifier guarantees: - * - * * 12 bits for the entity number (up to 4k entities). - * * 4 bit for the version (resets in [0-15]). - */ -template<> -struct entt_traits { - /*! @brief Underlying entity type. */ - using entity_type = std::uint16_t; - /*! @brief Underlying version type. */ - using version_type = std::uint8_t; - /*! @brief Difference type. */ - using difference_type = std::int32_t; - - /*! @brief Mask to use to get the entity number out of an identifier. */ - static constexpr std::uint16_t entity_mask = 0xFFF; - /*! @brief Mask to use to get the version out of an identifier. */ - static constexpr std::uint16_t version_mask = 0xF; - /*! @brief Extent of the entity number within an identifier. */ - static constexpr auto entity_shift = 12; -}; +template +struct entt_traits; +template +struct entt_traits>> + : entt_traits> {}; + +template +struct entt_traits>> + : entt_traits {}; -/** - * @brief Entity traits for a 32 bits entity identifier. - * - * A 32 bits entity identifier guarantees: - * - * * 20 bits for the entity number (suitable for almost all the games). - * * 12 bit for the version (resets in [0-4095]). - */ template<> struct entt_traits { - /*! @brief Underlying entity type. */ using entity_type = std::uint32_t; - /*! @brief Underlying version type. */ using version_type = std::uint16_t; - /*! @brief Difference type. */ - using difference_type = std::int64_t; - - /*! @brief Mask to use to get the entity number out of an identifier. */ - static constexpr std::uint32_t entity_mask = 0xFFFFF; - /*! @brief Mask to use to get the version out of an identifier. */ - static constexpr std::uint32_t version_mask = 0xFFF; - /*! @brief Extent of the entity number within an identifier. */ - static constexpr auto entity_shift = 20; -}; + static constexpr entity_type entity_mask = 0xFFFFF; + static constexpr entity_type version_mask = 0xFFF; + static constexpr std::size_t entity_shift = 20u; +}; -/** - * @brief Entity traits for a 64 bits entity identifier. - * - * A 64 bits entity identifier guarantees: - * - * * 32 bits for the entity number (an indecently large number). - * * 32 bit for the version (an indecently large number). - */ template<> struct entt_traits { - /*! @brief Underlying entity type. */ using entity_type = std::uint64_t; - /*! @brief Underlying version type. */ using version_type = std::uint32_t; - /*! @brief Difference type. */ - using difference_type = std::int64_t; - - /*! @brief Mask to use to get the entity number out of an identifier. */ - static constexpr std::uint64_t entity_mask = 0xFFFFFFFF; - /*! @brief Mask to use to get the version out of an identifier. */ - static constexpr std::uint64_t version_mask = 0xFFFFFFFF; - /*! @brief Extent of the entity number within an identifier. */ - static constexpr auto entity_shift = 32; + + static constexpr entity_type entity_mask = 0xFFFFFFFF; + static constexpr entity_type version_mask = 0xFFFFFFFF; + static constexpr std::size_t entity_shift = 32u; }; +} // namespace internal /** - * @cond TURN_OFF_DOXYGEN * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Entity traits. + * @tparam Type Type of identifier. */ +template +class entt_traits: internal::entt_traits { + using base_type = internal::entt_traits; +public: + /*! @brief Value type. */ + using value_type = Type; + /*! @brief Underlying entity type. */ + using entity_type = typename base_type::entity_type; + /*! @brief Underlying version type. */ + using version_type = typename base_type::version_type; + /*! @brief Reserved identifier. */ + static constexpr entity_type reserved = base_type::entity_mask | (base_type::version_mask << base_type::entity_shift); + /*! @brief Page size, default is `ENTT_SPARSE_PAGE`. */ + static constexpr auto page_size = ENTT_SPARSE_PAGE; + + /** + * @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) ENTT_NOEXCEPT { + return static_cast(value); + } -namespace internal { + /** + * @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) ENTT_NOEXCEPT { + return (to_integral(value) & base_type::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) ENTT_NOEXCEPT { + return (to_integral(value) >> base_type::entity_shift); + } + /** + * @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) ENTT_NOEXCEPT { + return value_type{(entity & base_type::entity_mask) | (static_cast(version) << base_type::entity_shift)}; + } -struct null { + /** + * @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) ENTT_NOEXCEPT { + constexpr auto mask = (base_type::version_mask << base_type::entity_shift); + return value_type{(lhs & base_type::entity_mask) | (rhs & mask)}; + } +}; + +/** + * @copydoc entt_traits::to_integral + * @tparam Entity The value type. + */ +template +[[nodiscard]] constexpr typename entt_traits::entity_type to_integral(const Entity value) ENTT_NOEXCEPT { + return entt_traits::to_integral(value); +} + +/** + * @copydoc entt_traits::to_entity + * @tparam Entity The value type. + */ +template +[[nodiscard]] constexpr typename entt_traits::entity_type to_entity(const Entity value) ENTT_NOEXCEPT { + return entt_traits::to_entity(value); +} + +/** + * @copydoc entt_traits::to_version + * @tparam Entity The value type. + */ +template +[[nodiscard]] constexpr typename entt_traits::version_type to_version(const Entity value) ENTT_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 - constexpr operator Entity() const ENTT_NOEXCEPT { - using traits_type = entt_traits; - return traits_type::entity_mask | (traits_type::version_mask << traits_type::entity_shift); + [[nodiscard]] constexpr operator Entity() const ENTT_NOEXCEPT { + using entity_traits = entt_traits; + return entity_traits::combine(entity_traits::reserved, entity_traits::reserved); } - constexpr bool operator==(null) const ENTT_NOEXCEPT { + /** + * @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 ENTT_NOEXCEPT { return true; } - constexpr bool operator!=(null) const ENTT_NOEXCEPT { + /** + * @brief Compares two null objects. + * @param other A null object. + * @return False in all cases. + */ + [[nodiscard]] constexpr bool operator!=([[maybe_unused]] const null_t other) const ENTT_NOEXCEPT { return false; } + /** + * @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 - constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT { - return entity == static_cast(*this); + [[nodiscard]] constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT { + using entity_traits = entt_traits; + return entity_traits::to_entity(entity) == entity_traits::to_entity(*this); } + /** + * @brief Compares a null object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @return True if the two elements differ, false otherwise. + */ template - constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT { - return entity != static_cast(*this); + [[nodiscard]] constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT { + return !(entity == *this); } }; - +/** + * @brief Compares a null object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @param other A null object yet to be converted. + * @return False if the two elements differ, true otherwise. + */ template -constexpr bool operator==(const Entity entity, null other) ENTT_NOEXCEPT { - return other == entity; +[[nodiscard]] constexpr bool operator==(const Entity entity, const null_t other) ENTT_NOEXCEPT { + return other.operator==(entity); } - +/** + * @brief Compares a null object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @param other A null object yet to be converted. + * @return True if the two elements differ, false otherwise. + */ template -constexpr bool operator!=(const Entity entity, null other) ENTT_NOEXCEPT { - return other != entity; +[[nodiscard]] constexpr bool operator!=(const Entity entity, const null_t other) ENTT_NOEXCEPT { + return !(other == entity); } +/*! @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 ENTT_NOEXCEPT { + using entity_traits = entt_traits; + return entity_traits::combine(entity_traits::reserved, entity_traits::reserved); + } + + /** + * @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 ENTT_NOEXCEPT { + return true; + } -} + /** + * @brief Compares two tombstone objects. + * @param other A tombstone object. + * @return False in all cases. + */ + [[nodiscard]] constexpr bool operator!=([[maybe_unused]] const tombstone_t other) const ENTT_NOEXCEPT { + return false; + } + /** + * @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 ENTT_NOEXCEPT { + using entity_traits = entt_traits; + return entity_traits::to_version(entity) == entity_traits::to_version(*this); + } + + /** + * @brief Compares a tombstone object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @return True if the two elements differ, false otherwise. + */ + template + [[nodiscard]] constexpr bool operator!=(const Entity entity) const ENTT_NOEXCEPT { + return !(entity == *this); + } +}; /** - * Internal details not to be documented. - * @endcond TURN_OFF_DOXYGEN + * @brief Compares a tombstone object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @param other A tombstone object yet to be converted. + * @return False if the two elements differ, true otherwise. */ +template +[[nodiscard]] constexpr bool operator==(const Entity entity, const tombstone_t other) ENTT_NOEXCEPT { + return other.operator==(entity); +} +/** + * @brief Compares a tombstone object and an identifier of any type. + * @tparam Entity Type of identifier. + * @param entity Identifier with which to compare. + * @param other A tombstone object yet to be converted. + * @return True if the two elements differ, false otherwise. + */ +template +[[nodiscard]] constexpr bool operator!=(const Entity entity, const tombstone_t other) ENTT_NOEXCEPT { + return !(other == entity); +} /** - * @brief Null entity. + * @brief Compile-time constant for null entities. * - * There exist implicit conversions from this variable to entity identifiers of - * any allowed type. Similarly, there exist comparision operators between the - * null entity and any other entity identifier. + * 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. */ -constexpr auto null = internal::null{}; - +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 // ENTT_ENTITY_ENTITY_HPP +#endif diff --git a/modules/entt/src/entt/entity/fwd.hpp b/modules/entt/src/entt/entity/fwd.hpp index c455a40..3f84419 100644 --- a/modules/entt/src/entt/entity/fwd.hpp +++ b/modules/entt/src/entt/entity/fwd.hpp @@ -1,60 +1,89 @@ #ifndef ENTT_ENTITY_FWD_HPP #define ENTT_ENTITY_FWD_HPP +#include +#include "../core/fwd.hpp" +#include "utility.hpp" -#include -#include "../config/config.h" +namespace entt { +template> +class basic_sparse_set; -namespace entt { +template, typename = void> +class basic_storage; -/*! @class basic_registry */ -template +template class basic_registry; -/*! @class basic_view */ -template +template class basic_view; -/*! @class basic_runtime_view */ template -class basic_runtime_view; +struct basic_runtime_view; -/*! @class basic_group */ -template +template class basic_group; -/*! @class basic_actor */ -template -struct basic_actor; +template +class basic_observer; -/*! @class basic_prototype */ template -class basic_prototype; +class basic_organizer; + +template +struct basic_handle; -/*! @class basic_snapshot */ template class basic_snapshot; -/*! @class basic_snapshot_loader */ template class basic_snapshot_loader; -/*! @class basic_continuous_loader */ template class basic_continuous_loader; +/*! @brief Default entity identifier. */ +enum class entity : id_type {}; + /*! @brief Alias declaration for the most common use case. */ -using entity = std::uint32_t; +using sparse_set = basic_sparse_set; + +/** + * @brief Alias declaration for the most common use case. + * @tparam Args Other template parameters. + */ +template +using storage = basic_storage; /*! @brief Alias declaration for the most common use case. */ using registry = basic_registry; /*! @brief Alias declaration for the most common use case. */ -using actor = basic_actor; +using observer = basic_observer; + +/*! @brief Alias declaration for the most common use case. */ +using organizer = basic_organizer; /*! @brief Alias declaration for the most common use case. */ -using prototype = basic_prototype; +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; @@ -67,23 +96,22 @@ using continuous_loader = basic_continuous_loader; /** * @brief Alias declaration for the most common use case. - * @tparam Component Types of components iterated by the view. + * @tparam Get Types of components iterated by the view. + * @tparam Exclude Types of components used to filter the view. */ -template -using view = basic_view; +template> +using view = basic_view; /*! @brief Alias declaration for the most common use case. */ -using runtime_view = basic_runtime_view; +using runtime_view = basic_runtime_view; /** * @brief Alias declaration for the most common use case. - * @tparam Types Types of components iterated by the group. + * @tparam Args Other template parameters. */ -template -using group = basic_group; - - -} +template +using group = basic_group; +} // namespace entt -#endif // ENTT_ENTITY_FWD_HPP +#endif diff --git a/modules/entt/src/entt/entity/group.hpp b/modules/entt/src/entt/entity/group.hpp index a6899d9..6d056a2 100644 --- a/modules/entt/src/entt/entity/group.hpp +++ b/modules/entt/src/entt/entity/group.hpp @@ -1,54 +1,36 @@ #ifndef ENTT_ENTITY_GROUP_HPP #define ENTT_ENTITY_GROUP_HPP - #include -#include #include +#include #include "../config/config.h" +#include "../core/iterator.hpp" #include "../core/type_traits.hpp" +#include "component.hpp" +#include "entity.hpp" +#include "fwd.hpp" #include "sparse_set.hpp" #include "storage.hpp" -#include "fwd.hpp" - +#include "utility.hpp" namespace entt { - -/** - * @brief Alias for lists of observed components. - * @tparam Type List of types. - */ -template -struct get_t: type_list {}; - - -/** - * @brief Variable template for lists of observed components. - * @tparam Type List of types. - */ -template -constexpr get_t get{}; - - /** * @brief Group. * * Primary template isn't defined on purpose. All the specializations give a * compile-time error, but for a few reasonable cases. */ -template +template class basic_group; - /** * @brief Non-owning group. * - * A non-owning group returns all the entities and only the entities that have - * at least the given components. Moreover, it's guaranteed that the entity list - * is tightly packed in memory for fast iterations.
- * In general, non-owning groups don't stay true to the order of any set of - * components unless users explicitly sort them. + * A non-owning group returns all entities and only the entities that have at + * least the given components. Moreover, it's guaranteed that the entity list + * is tightly packed in memory for fast iterations. * * @b Important * @@ -57,78 +39,138 @@ class basic_group; * * New instances of the given components are created and assigned to entities. * * The entity currently pointed is modified (as an example, if one of the * given components is removed from the entity to which the iterator points). + * * The entity currently pointed is destroyed. * - * In all the other cases, modifying the pools of the given components in any - * way invalidates all the iterators and using them results in undefined - * behavior. + * In all other cases, modifying the pools iterated by the group in any way + * invalidates all the iterators and using them results in undefined behavior. * * @note * Groups share references to the underlying data structures of the registry * that generated them. Therefore any change to the entities and to the * components made by means of the registry are immediately reflected by all the * groups.
- * Moreover, sorting a non-owning group affects all the instance of the same + * Moreover, sorting a non-owning group affects all the instances of the same * group (it means that users don't have to call `sort` on each instance to sort - * all of them because they share the set of entities). + * all of them because they _share_ entities and components). * * @warning - * Lifetime of a group must overcome the one of the registry that generated it. + * Lifetime of a group must not overcome that of the registry that generated it. * In any other case, attempting to use a group results in undefined behavior. * * @tparam Entity A valid entity type (see entt_traits for more details). - * @tparam Get Types of components observed by the group. + * @tparam Get Type of components observed by the group. + * @tparam Exclude Types of components used to filter the group. */ -template -class basic_group> { - static_assert(sizeof...(Get) > 0); - +template +class basic_group, get_t, exclude_t> { /*! @brief A registry is allowed to create groups. */ friend class basic_registry; - template - using pool_type = std::conditional_t, const storage>, storage>; + template + using storage_type = constness_as_t>::storage_type, Comp>; - // we could use pool_type *..., but vs complains about it and refuses to compile for unknown reasons (likely a bug) - basic_group(sparse_set *ref, storage> *... get) ENTT_NOEXCEPT - : handler{ref}, - pools{get...} - {} + using basic_common_type = std::common_type_t::base_type...>; - template - inline void traverse(Func func, type_list) const { - for(const auto entt: *handler) { - if constexpr(std::is_invocable_v({}))...>) { - func(std::get *>(pools)->get(entt)...); - } else { - func(entt, std::get *>(pools)->get(entt)...); - } - }; - } + struct extended_group_iterator final { + using difference_type = std::ptrdiff_t; + using value_type = decltype(std::tuple_cat(std::tuple{}, std::declval().get({}))); + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + extended_group_iterator() = default; + + extended_group_iterator(typename basic_common_type::iterator from, const std::tuple *...> &args) + : it{from}, + pools{args} {} + + extended_group_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } + + extended_group_iterator operator++(int) ENTT_NOEXCEPT { + extended_group_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + const auto entt = *it; + return std::tuple_cat(std::make_tuple(entt), std::get *>(pools)->get_as_tuple(entt)...); + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + [[nodiscard]] bool operator==(const extended_group_iterator &other) const ENTT_NOEXCEPT { + return other.it == it; + } + + [[nodiscard]] bool operator!=(const extended_group_iterator &other) const ENTT_NOEXCEPT { + return !(*this == other); + } + + private: + typename basic_common_type::iterator it; + std::tuple *...> pools; + }; + + basic_group(basic_common_type &ref, storage_type &...gpool) ENTT_NOEXCEPT + : handler{&ref}, + pools{&gpool...} {} public: /*! @brief Underlying entity identifier. */ - using entity_type = typename sparse_set::entity_type; + using entity_type = Entity; /*! @brief Unsigned integer type. */ - using size_type = typename sparse_set::size_type; - /*! @brief Input iterator type. */ - using iterator_type = typename sparse_set::iterator_type; + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using base_type = basic_common_type; + /*! @brief Random access iterator type. */ + using iterator = typename base_type::iterator; + /*! @brief Reversed iterator type. */ + using reverse_iterator = typename base_type::reverse_iterator; + /*! @brief Iterable group type. */ + using iterable = iterable_adaptor; + + /*! @brief Default constructor to use to create empty, invalid groups. */ + basic_group() ENTT_NOEXCEPT + : handler{} {} + + /** + * @brief Returns a const reference to the underlying handler. + * @return A const reference to the underlying handler. + */ + const base_type &handle() const ENTT_NOEXCEPT { + return *handler; + } /** - * @brief Returns the number of existing components of the given type. - * @tparam Component Type of component of which to return the size. - * @return Number of existing components of the given type. + * @brief Returns the storage for a given component type. + * @tparam Comp Type of component of which to return the storage. + * @return The storage for the given component type. */ - template - size_type size() const ENTT_NOEXCEPT { - return std::get *>(pools)->size(); + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get *>(pools); + } + + /** + * @brief Returns the storage for a given component type. + * @tparam Comp Index of component of which to return the storage. + * @return The storage for the given component type. + */ + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get(pools); } /** * @brief Returns the number of entities that have the given components. * @return Number of entities that have the given components. */ - size_type size() const ENTT_NOEXCEPT { - return handler->size(); + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return *this ? handler->size() : size_type{}; } /** @@ -136,133 +178,106 @@ public: * allocated space for. * @return Capacity of the group. */ - size_type capacity() const ENTT_NOEXCEPT { - return handler->capacity(); + [[nodiscard]] size_type capacity() const ENTT_NOEXCEPT { + return *this ? handler->capacity() : size_type{}; } /*! @brief Requests the removal of unused capacity. */ void shrink_to_fit() { - handler->shrink_to_fit(); + if(*this) { + handler->shrink_to_fit(); + } } /** - * @brief Checks whether the pool of a given component is empty. - * @tparam Component Type of component in which one is interested. - * @return True if the pool of the given component is empty, false - * otherwise. + * @brief Checks whether a group is empty. + * @return True if the group is empty, false otherwise. */ - template - bool empty() const ENTT_NOEXCEPT { - return std::get *>(pools)->empty(); + [[nodiscard]] bool empty() const ENTT_NOEXCEPT { + return !*this || handler->empty(); } /** - * @brief Checks whether the group is empty. - * @return True if the group is empty, false otherwise. + * @brief Returns an iterator to the first entity of the group. + * + * The returned iterator points 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. */ - bool empty() const ENTT_NOEXCEPT { - return handler->empty(); + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return *this ? handler->begin() : iterator{}; } /** - * @brief Direct access to the list of components of a given pool. + * @brief Returns an iterator that is past the last entity of the group. * - * The returned pointer is such that range - * `[raw(), raw() + size()]` is always a - * valid range, even if the container is empty. + * The returned iterator points to the entity following the last entity of + * the group. Attempting to dereference the returned iterator results in + * undefined behavior. * - * @note - * There are no guarantees on the order of the components. Use `begin` and - * `end` if you want to iterate the group in the expected order. - * - * @tparam Component Type of component in which one is interested. - * @return A pointer to the array of components. + * @return An iterator to the entity following the last entity of the + * group. */ - template - Component * raw() const ENTT_NOEXCEPT { - return std::get *>(pools)->raw(); + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return *this ? handler->end() : iterator{}; } /** - * @brief Direct access to the list of entities of a given pool. + * @brief Returns an iterator to the first entity of the reversed group. * - * The returned pointer is such that range - * `[data(), data() + size()]` is always a - * valid range, even if the container is empty. + * The returned iterator points to the first entity of the reversed group. + * If the group is empty, the returned iterator will be equal to `rend()`. * - * @note - * There are no guarantees on the order of the entities. Use `begin` and - * `end` if you want to iterate the group in the expected order. - * - * @tparam Component Type of component in which one is interested. - * @return A pointer to the array of entities. + * @return An iterator to the first entity of the reversed group. */ - template - const entity_type * data() const ENTT_NOEXCEPT { - return std::get *>(pools)->data(); + [[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT { + return *this ? handler->rbegin() : reverse_iterator{}; } /** - * @brief Direct access to the list of entities. - * - * The returned pointer is such that range `[data(), data() + size()]` is - * always a valid range, even if the container is empty. + * @brief Returns an iterator that is past the last entity of the reversed + * group. * - * @note - * There are no guarantees on the order of the entities. Use `begin` and - * `end` if you want to iterate the group in the expected order. + * The returned iterator points to the entity following the last entity of + * the reversed group. Attempting to dereference the returned iterator + * results in undefined behavior. * - * @return A pointer to the array of entities. + * @return An iterator to the entity following the last entity of the + * reversed group. */ - const entity_type * data() const ENTT_NOEXCEPT { - return handler->data(); + [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT { + return *this ? handler->rend() : reverse_iterator{}; } /** - * @brief Returns an iterator to the first entity that has the given - * components. - * - * The returned iterator points to the first entity that has the given - * components. If the group is empty, the returned iterator will be equal to - * `end()`. - * - * @note - * Input iterators stay true to the order imposed to the underlying data - * structures. - * - * @return An iterator to the first entity that has the given components. + * @brief Returns the first entity of the group, if any. + * @return The first entity of the group if one exists, the null entity + * otherwise. */ - iterator_type begin() const ENTT_NOEXCEPT { - return handler->begin(); + [[nodiscard]] entity_type front() const ENTT_NOEXCEPT { + const auto it = begin(); + return it != end() ? *it : null; } /** - * @brief Returns an iterator that is past the last entity that has the - * given components. - * - * The returned iterator points to the entity following the last entity that - * has the given components. Attempting to dereference the returned iterator - * results in undefined behavior. - * - * @note - * Input iterators stay true to the order imposed to the underlying data - * structures. - * - * @return An iterator to the entity following the last entity that has the - * given components. + * @brief Returns the last entity of the group, if any. + * @return The last entity of the group if one exists, the null entity + * otherwise. */ - iterator_type end() const ENTT_NOEXCEPT { - return handler->end(); + [[nodiscard]] entity_type back() const ENTT_NOEXCEPT { + const auto it = rbegin(); + return it != rend() ? *it : null; } /** * @brief Finds an entity. - * @param entt A valid entity identifier. + * @param entt A valid identifier. * @return An iterator to the given entity if it's found, past the end * iterator otherwise. */ - iterator_type find(const entity_type entt) const ENTT_NOEXCEPT { - const auto it = handler->find(entt); + [[nodiscard]] iterator find(const entity_type entt) const ENTT_NOEXCEPT { + const auto it = *this ? handler->find(entt) : iterator{}; return it != end() && *it == entt ? it : end(); } @@ -271,44 +286,52 @@ public: * @param pos Position of the element to return. * @return The identifier that occupies the given position. */ - entity_type operator[](const size_type pos) const ENTT_NOEXCEPT { + [[nodiscard]] entity_type operator[](const size_type pos) const { return begin()[pos]; } + /** + * @brief Checks if a group is properly initialized. + * @return True if the group is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return handler != nullptr; + } + /** * @brief Checks if a group contains an entity. - * @param entt A valid entity identifier. + * @param entt A valid identifier. * @return True if the group contains the given entity, false otherwise. */ - bool contains(const entity_type entt) const ENTT_NOEXCEPT { - return find(entt) != end(); + [[nodiscard]] bool contains(const entity_type entt) const ENTT_NOEXCEPT { + return *this && handler->contains(entt); } /** * @brief Returns the components assigned to the given entity. * * Prefer this function instead of `registry::get` during iterations. It has - * far better performance than its companion function. + * far better performance than its counterpart. * * @warning * Attempting to use an invalid component type results in a compilation * error. Attempting to use an entity that doesn't belong to the group - * results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * group doesn't contain the given entity. + * results in undefined behavior. * - * @tparam Component Types of components to get. - * @param entt A valid entity identifier. + * @tparam Comp Types of components to get. + * @param entt A valid identifier. * @return The components assigned to the entity. */ - template - decltype(auto) get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { - ENTT_ASSERT(contains(entt)); + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + ENTT_ASSERT(contains(entt), "Group does not contain entity"); - if constexpr(sizeof...(Component) == 1) { - return (std::get *>(pools)->get(entt), ...); + if constexpr(sizeof...(Comp) == 0) { + return std::tuple_cat(std::get *>(pools)->get_as_tuple(entt)...); + } else if constexpr(sizeof...(Comp) == 1) { + return (std::get *>(pools)->get(entt), ...); } else { - return std::tuple(entt))...>{get(entt)...}; + return std::tuple_cat(std::get *>(pools)->get_as_tuple(entt)...); } } @@ -317,60 +340,113 @@ public: * object to them. * * The function object is invoked for each entity. It is provided with the - * entity itself and a set of references to all its components. The + * entity itself and a set of references to non-empty components. The * _constness_ of the components is as requested.
* The signature of the function must be equivalent to one of the following * forms: * * @code{.cpp} - * void(const entity_type, Get &...); - * void(Get &...); + * void(const entity_type, Type &...); + * void(Type &...); * @endcode * * @note - * Empty types aren't explicitly instantiated. Therefore, temporary objects - * are returned during iterations. They can be caught only by copy or with - * const references. + * 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 - inline void each(Func func) const { - traverse(std::move(func), type_list{}); + void each(Func func) const { + for(const auto entt: *this) { + if constexpr(is_applicable_v{}, std::declval().get({})))>) { + std::apply(func, std::tuple_cat(std::make_tuple(entt), get(entt))); + } else { + std::apply(func, get(entt)); + } + } } /** - * @brief Iterates entities and components and applies the given function - * object to them. + * @brief Returns an iterable object to use to _visit_ a group. * - * The function object is invoked for each entity. It is provided with the - * entity itself and a set of references to non-empty components. The - * _constness_ of the components is as requested.
- * The signature of the function must be equivalent to one of the following - * forms: + * The iterable object returns tuples that contain the current entity and a + * set of references to its non-empty components. The _constness_ of the + * components 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 ENTT_NOEXCEPT { + return handler ? iterable{extended_group_iterator{handler->begin(), pools}, extended_group_iterator{handler->end(), pools}} + : iterable{extended_group_iterator{{}, pools}, extended_group_iterator{{}, pools}}; + } + + /** + * @brief Sort a group according to the given comparison function. + * + * Sort the group so that iterating it with a couple of iterators returns + * entities and components in the expected order. See `begin` and `end` for + * more details. + * + * 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} - * void(const entity_type, Type &...); - * void(Type &...); + * bool(std::tuple, std::tuple); + * bool(const Component &..., const Component &...); + * bool(const Entity, const Entity); * @endcode * - * @sa each + * Where `Component` 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: * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. + * * 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 Comp Optional types of components 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 - inline void less(Func func) const { - using non_empty_get = type_list_cat_t, type_list<>, type_list>...>; - traverse(std::move(func), non_empty_get{}); + template + void sort(Compare compare, Sort algo = Sort{}, Args &&...args) { + if(*this) { + if constexpr(sizeof...(Comp) == 0) { + static_assert(std::is_invocable_v, "Invalid comparison function"); + handler->sort(std::move(compare), std::move(algo), std::forward(args)...); + } else { + auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) { + if constexpr(sizeof...(Comp) == 1) { + return compare((std::get *>(pools)->get(lhs), ...), (std::get *>(pools)->get(rhs), ...)); + } else { + return compare(std::forward_as_tuple(std::get *>(pools)->get(lhs)...), std::forward_as_tuple(std::get *>(pools)->get(rhs)...)); + } + }; + + handler->sort(std::move(comp), std::move(algo), std::forward(args)...); + } + } } /** * @brief Sort the shared pool of entities according to the given component. * * Non-owning groups of the same type share with the registry a pool of - * entities with its own order that doesn't depend on the order of any pool + * entities with its own order that doesn't depend on the order of any pool * of components. Users can order the underlying data structure so that it * respects the order of the pool of the given component. * @@ -380,31 +456,32 @@ public: * can quickly ruin the order imposed to the pool of entities shared between * the non-owning groups. * - * @tparam Component Type of component to use to impose the order. + * @tparam Comp Type of component to use to impose the order. */ - template + template void sort() const { - handler->respect(*std::get *>(pools)); + if(*this) { + handler->respect(*std::get *>(pools)); + } } private: - sparse_set *handler; - const std::tuple *...> pools; + base_type *const handler; + const std::tuple *...> pools; }; - /** * @brief Owning group. * - * Owning groups return all the entities and only the entities that have at - * least the given components. Moreover: + * Owning groups return all entities and only the entities that have at least + * the given components. Moreover: * * * It's guaranteed that the entity list is tightly packed in memory for fast * iterations. * * It's guaranteed that the lists of owned components are tightly packed in * memory for even faster iterations and to allow direct access. - * * They stay true to the order of the owned components and all the owned - * components have the same order in memory. + * * They stay true to the order of the owned components and all instances have + * the same order in memory. * * The more types of components are owned by a group, the faster it is to * iterate them. @@ -416,10 +493,10 @@ private: * * New instances of the given components are created and assigned to entities. * * The entity currently pointed is modified (as an example, if one of the * given components is removed from the entity to which the iterator points). + * * The entity currently pointed is destroyed. * - * In all the other cases, modifying the pools of the given components in any - * way invalidates all the iterators and using them results in undefined - * behavior. + * In all other cases, modifying the pools iterated by the group in any way + * invalidates all the iterators and using them results in undefined behavior. * * @note * Groups share references to the underlying data structures of the registry @@ -431,202 +508,220 @@ private: * of them because they share the underlying data structure). * * @warning - * Lifetime of a group must overcome the one of the registry that generated it. + * Lifetime of a group must not overcome that of the registry that generated it. * In any other case, attempting to use a group results in undefined behavior. * * @tparam Entity A valid entity type (see entt_traits for more details). - * @tparam Get Types of components observed by the group. * @tparam Owned Types of components owned by the group. + * @tparam Get Types of components observed by the group. + * @tparam Exclude Types of components used to filter the group. */ -template -class basic_group, Owned...> { - static_assert(sizeof...(Get) + sizeof...(Owned) > 0); - +template +class basic_group, get_t, exclude_t> { /*! @brief A registry is allowed to create groups. */ friend class basic_registry; - template - using pool_type = std::conditional_t, const storage>, storage>; - - template - using component_iterator_type = decltype(std::declval>().begin()); + template + using storage_type = constness_as_t>::storage_type, Comp>; - // we could use pool_type *..., but vs complains about it and refuses to compile for unknown reasons (likely a bug) - basic_group(const typename basic_registry::size_type *sz, storage> *... owned, storage> *... get) ENTT_NOEXCEPT - : length{sz}, - pools{owned..., get...} - {} + using basic_common_type = std::common_type_t::base_type..., typename storage_type::base_type...>; - template - inline void traverse(Func func, type_list, type_list) const { - auto raw = std::make_tuple((std::get *>(pools)->end() - *length)...); - [[maybe_unused]] auto data = std::get<0>(pools)->sparse_set::end() - *length; - - for(auto next = *length; next; --next) { - if constexpr(std::is_invocable_v({}))..., decltype(get({}))...>) { - if constexpr(sizeof...(Weak) == 0) { - func(*(std::get>(raw)++)...); - } else { - const auto entt = *(data++); - func(*(std::get>(raw)++)..., std::get *>(pools)->get(entt)...); - } + class extended_group_iterator final { + template + auto index_to_element(storage_type &cpool) const { + if constexpr(ignore_as_empty_v>) { + return std::make_tuple(); } else { - const auto entt = *(data++); - func(entt, *(std::get>(raw)++)..., std::get *>(pools)->get(entt)...); + return std::forward_as_tuple(cpool.rbegin()[it.index()]); } } - } + + public: + using difference_type = std::ptrdiff_t; + using value_type = decltype(std::tuple_cat(std::tuple{}, std::declval().get({}))); + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + extended_group_iterator() = default; + + template + extended_group_iterator(typename basic_common_type::iterator from, const std::tuple *..., storage_type *...> &cpools) + : it{from}, + pools{cpools} {} + + extended_group_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } + + extended_group_iterator operator++(int) ENTT_NOEXCEPT { + extended_group_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return std::tuple_cat( + std::make_tuple(*it), + index_to_element(*std::get *>(pools))..., + std::get *>(pools)->get_as_tuple(*it)...); + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + [[nodiscard]] bool operator==(const extended_group_iterator &other) const ENTT_NOEXCEPT { + return other.it == it; + } + + [[nodiscard]] bool operator!=(const extended_group_iterator &other) const ENTT_NOEXCEPT { + return !(*this == other); + } + + private: + typename basic_common_type::iterator it; + std::tuple *..., storage_type *...> pools; + }; + + basic_group(const std::size_t &extent, storage_type &...opool, storage_type &...gpool) ENTT_NOEXCEPT + : pools{&opool..., &gpool...}, + length{&extent} {} public: /*! @brief Underlying entity identifier. */ - using entity_type = typename sparse_set::entity_type; + using entity_type = Entity; /*! @brief Unsigned integer type. */ - using size_type = typename sparse_set::size_type; - /*! @brief Input iterator type. */ - using iterator_type = typename sparse_set::iterator_type; + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using base_type = basic_common_type; + /*! @brief Random access iterator type. */ + using iterator = typename base_type::iterator; + /*! @brief Reversed iterator type. */ + using reverse_iterator = typename base_type::reverse_iterator; + /*! @brief Iterable group type. */ + using iterable = iterable_adaptor; + + /*! @brief Default constructor to use to create empty, invalid groups. */ + basic_group() ENTT_NOEXCEPT + : length{} {} /** - * @brief Returns the number of existing components of the given type. - * @tparam Component Type of component of which to return the size. - * @return Number of existing components of the given type. + * @brief Returns the storage for a given component type. + * @tparam Comp Type of component of which to return the storage. + * @return The storage for the given component type. */ - template - size_type size() const ENTT_NOEXCEPT { - return std::get *>(pools)->size(); + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get *>(pools); } /** - * @brief Returns the number of entities that have the given components. - * @return Number of entities that have the given components. + * @brief Returns the storage for a given component type. + * @tparam Comp Index of component of which to return the storage. + * @return The storage for the given component type. */ - size_type size() const ENTT_NOEXCEPT { - return *length; + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get(pools); } /** - * @brief Checks whether the pool of a given component is empty. - * @tparam Component Type of component in which one is interested. - * @return True if the pool of the given component is empty, false - * otherwise. + * @brief Returns the number of entities that have the given components. + * @return Number of entities that have the given components. */ - template - bool empty() const ENTT_NOEXCEPT { - return std::get *>(pools)->empty(); + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return *this ? *length : size_type{}; } /** - * @brief Checks whether the group is empty. + * @brief Checks whether a group is empty. * @return True if the group is empty, false otherwise. */ - bool empty() const ENTT_NOEXCEPT { - return !*length; + [[nodiscard]] bool empty() const ENTT_NOEXCEPT { + return !*this || !*length; } /** - * @brief Direct access to the list of components of a given pool. + * @brief Returns an iterator to the first entity of the group. * - * The returned pointer is such that range - * `[raw(), raw() + size()]` is always a - * valid range, even if the container is empty.
- * Moreover, in case the group owns the given component, the range - * `[raw(), raw() + size()]` is such that it contains - * the instances that are part of the group itself. + * The returned iterator points to the first entity of the group. If the + * group is empty, the returned iterator will be equal to `end()`. * - * @note - * There are no guarantees on the order of the components. Use `begin` and - * `end` if you want to iterate the group in the expected order. - * - * @tparam Component Type of component in which one is interested. - * @return A pointer to the array of components. + * @return An iterator to the first entity of the group. */ - template - Component * raw() const ENTT_NOEXCEPT { - return std::get *>(pools)->raw(); + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return *this ? (std::get<0>(pools)->base_type::end() - *length) : iterator{}; } /** - * @brief Direct access to the list of entities of a given pool. + * @brief Returns an iterator that is past the last entity of the group. * - * The returned pointer is such that range - * `[data(), data() + size()]` is always a - * valid range, even if the container is empty.
- * Moreover, in case the group owns the given component, the range - * `[data(), data() + size()]` is such that it - * contains the entities that are part of the group itself. + * The returned iterator points to the entity following the last entity of + * the group. Attempting to dereference the returned iterator results in + * undefined behavior. * - * @note - * There are no guarantees on the order of the entities. Use `begin` and - * `end` if you want to iterate the group in the expected order. - * - * @tparam Component Type of component in which one is interested. - * @return A pointer to the array of entities. + * @return An iterator to the entity following the last entity of the + * group. */ - template - const entity_type * data() const ENTT_NOEXCEPT { - return std::get *>(pools)->data(); + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return *this ? std::get<0>(pools)->base_type::end() : iterator{}; } /** - * @brief Direct access to the list of entities. - * - * The returned pointer is such that range `[data(), data() + size()]` is - * always a valid range, even if the container is empty. + * @brief Returns an iterator to the first entity of the reversed group. * - * @note - * There are no guarantees on the order of the entities. Use `begin` and - * `end` if you want to iterate the group in the expected order. + * The returned iterator points to the first entity of the reversed group. + * If the group is empty, the returned iterator will be equal to `rend()`. * - * @return A pointer to the array of entities. + * @return An iterator to the first entity of the reversed group. */ - const entity_type * data() const ENTT_NOEXCEPT { - return std::get<0>(pools)->data(); + [[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT { + return *this ? std::get<0>(pools)->base_type::rbegin() : reverse_iterator{}; } /** - * @brief Returns an iterator to the first entity that has the given - * components. - * - * The returned iterator points to the first entity that has the given - * components. If the group is empty, the returned iterator will be equal to - * `end()`. + * @brief Returns an iterator that is past the last entity of the reversed + * group. * - * @note - * Input iterators stay true to the order imposed to the underlying data - * structures. + * The returned iterator points to the entity following the last entity of + * the reversed group. Attempting to dereference the returned iterator + * results in undefined behavior. * - * @return An iterator to the first entity that has the given components. + * @return An iterator to the entity following the last entity of the + * reversed group. */ - iterator_type begin() const ENTT_NOEXCEPT { - return std::get<0>(pools)->sparse_set::end() - *length; + [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT { + return *this ? (std::get<0>(pools)->base_type::rbegin() + *length) : reverse_iterator{}; } /** - * @brief Returns an iterator that is past the last entity that has the - * given components. - * - * The returned iterator points to the entity following the last entity that - * has the given components. Attempting to dereference the returned iterator - * results in undefined behavior. - * - * @note - * Input iterators stay true to the order imposed to the underlying data - * structures. - * - * @return An iterator to the entity following the last entity that has the - * given components. + * @brief Returns the first entity of the group, if any. + * @return The first entity of the group if one exists, the null entity + * otherwise. */ - iterator_type end() const ENTT_NOEXCEPT { - return std::get<0>(pools)->sparse_set::end(); + [[nodiscard]] entity_type front() const ENTT_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 ENTT_NOEXCEPT { + const auto it = rbegin(); + return it != rend() ? *it : null; } /** * @brief Finds an entity. - * @param entt A valid entity identifier. + * @param entt A valid identifier. * @return An iterator to the given entity if it's found, past the end * iterator otherwise. */ - iterator_type find(const entity_type entt) const ENTT_NOEXCEPT { - const auto it = std::get<0>(pools)->find(entt); + [[nodiscard]] iterator find(const entity_type entt) const ENTT_NOEXCEPT { + const auto it = *this ? std::get<0>(pools)->find(entt) : iterator{}; return it != end() && it >= begin() && *it == entt ? it : end(); } @@ -635,44 +730,52 @@ public: * @param pos Position of the element to return. * @return The identifier that occupies the given position. */ - entity_type operator[](const size_type pos) const ENTT_NOEXCEPT { + [[nodiscard]] entity_type operator[](const size_type pos) const { return begin()[pos]; } + /** + * @brief Checks if a group is properly initialized. + * @return True if the group is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return length != nullptr; + } + /** * @brief Checks if a group contains an entity. - * @param entt A valid entity identifier. + * @param entt A valid identifier. * @return True if the group contains the given entity, false otherwise. */ - bool contains(const entity_type entt) const ENTT_NOEXCEPT { - return find(entt) != end(); + [[nodiscard]] bool contains(const entity_type entt) const ENTT_NOEXCEPT { + return *this && std::get<0>(pools)->contains(entt) && (std::get<0>(pools)->index(entt) < (*length)); } /** * @brief Returns the components assigned to the given entity. * * Prefer this function instead of `registry::get` during iterations. It has - * far better performance than its companion function. + * far better performance than its counterpart. * * @warning * Attempting to use an invalid component type results in a compilation * error. Attempting to use an entity that doesn't belong to the group - * results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * group doesn't contain the given entity. + * results in undefined behavior. * - * @tparam Component Types of components to get. - * @param entt A valid entity identifier. + * @tparam Comp Types of components to get. + * @param entt A valid identifier. * @return The components assigned to the entity. */ - template - decltype(auto) get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { - ENTT_ASSERT(contains(entt)); + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + ENTT_ASSERT(contains(entt), "Group does not contain entity"); - if constexpr(sizeof...(Component) == 1) { - return (std::get *>(pools)->get(entt), ...); + if constexpr(sizeof...(Comp) == 0) { + return std::tuple_cat(std::get *>(pools)->get_as_tuple(entt)..., std::get *>(pools)->get_as_tuple(entt)...); + } else if constexpr(sizeof...(Comp) == 1) { + return (std::get *>(pools)->get(entt), ...); } else { - return std::tuple(entt))...>{get(entt)...}; + return std::tuple_cat(std::get *>(pools)->get_as_tuple(entt)...); } } @@ -681,54 +784,50 @@ public: * object to them. * * The function object is invoked for each entity. It is provided with the - * entity itself and a set of references to all its components. The + * entity itself and a set of references to non-empty components. The * _constness_ of the components is as requested.
* The signature of the function must be equivalent to one of the following * forms: * * @code{.cpp} - * void(const entity_type, Owned &..., Get &...); - * void(Owned &..., Get &...); + * void(const entity_type, Type &...); + * void(Type &...); * @endcode * * @note - * Empty types aren't explicitly instantiated. Therefore, temporary objects - * are returned during iterations. They can be caught only by copy or with - * const references. + * 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 - inline void each(Func func) const { - traverse(std::move(func), type_list{}, type_list{}); + void each(Func func) const { + for(auto args: each()) { + if constexpr(is_applicable_v{}, std::declval().get({})))>) { + std::apply(func, args); + } else { + std::apply([&func](auto, auto &&...less) { func(std::forward(less)...); }, args); + } + } } /** - * @brief Iterates entities and components 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 components. The - * _constness_ of the components is as requested.
- * The signature of the function must be equivalent to one of the following - * forms: + * @brief Returns an iterable object to use to _visit_ a group. * - * @code{.cpp} - * void(const entity_type, Type &...); - * void(Type &...); - * @endcode + * The iterable object returns tuples that contain the current entity and a + * set of references to its non-empty components. The _constness_ of the + * components is as requested. * - * @sa each + * @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. + * @return An iterable object to use to _visit_ the group. */ - template - inline void less(Func func) const { - using non_empty_owned = type_list_cat_t, type_list<>, type_list>...>; - using non_empty_get = type_list_cat_t, type_list<>, type_list>...>; - traverse(std::move(func), non_empty_owned{}, non_empty_get{}); + [[nodiscard]] iterable each() const ENTT_NOEXCEPT { + iterator last = length ? std::get<0>(pools)->basic_common_type::end() : iterator{}; + return {extended_group_iterator{last - *length, pools}, extended_group_iterator{last, pools}}; } /** @@ -743,7 +842,8 @@ public: * comparison function should be equivalent to one of the following: * * @code{.cpp} - * bool(const Component &..., const Component &...); + * bool(std::tuple, std::tuple); + * bool(const Component &, const Component &); * bool(const Entity, const Entity); * @endcode * @@ -752,23 +852,14 @@ public: * Moreover, the comparison function object shall induce a * _strict weak ordering_ on the values. * - * The sort function oject must offer a member function template + * 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. * - * The comparison function object received by the sort function object - * hasn't necessarily the type of the one passed along with the other - * parameters to this member function. - * - * @note - * Attempting to iterate elements using a raw pointer returned by a call to - * either `data` or `raw` gives no guarantees on the order, even though - * `sort` has been invoked. - * - * @tparam Component Optional types of components to compare. + * @tparam Comp Optional types of components 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. @@ -776,44 +867,39 @@ public: * @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) { - std::vector copy(*length); - std::iota(copy.begin(), copy.end(), 0); + template + void sort(Compare compare, Sort algo = Sort{}, Args &&...args) const { + auto *cpool = std::get<0>(pools); - if constexpr(sizeof...(Component) == 0) { - algo(copy.rbegin(), copy.rend(), [compare = std::move(compare), data = data()](const auto lhs, const auto rhs) { - return compare(data[lhs], data[rhs]); - }, std::forward(args)...); + if constexpr(sizeof...(Comp) == 0) { + static_assert(std::is_invocable_v, "Invalid comparison function"); + cpool->sort_n(*length, std::move(compare), std::move(algo), std::forward(args)...); } else { - algo(copy.rbegin(), copy.rend(), [compare = std::move(compare), this](const auto lhs, const auto rhs) { - // useless this-> used to suppress a warning with clang - return compare(this->get(lhs)..., this->get(rhs)...); - }, std::forward(args)...); + auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) { + if constexpr(sizeof...(Comp) == 1) { + return compare((std::get *>(pools)->get(lhs), ...), (std::get *>(pools)->get(rhs), ...)); + } else { + return compare(std::forward_as_tuple(std::get *>(pools)->get(lhs)...), std::forward_as_tuple(std::get *>(pools)->get(rhs)...)); + } + }; + + cpool->sort_n(*length, std::move(comp), std::move(algo), std::forward(args)...); } - for(size_type pos{}, last = copy.size(); pos < last; ++pos) { - auto curr = pos; - auto next = copy[curr]; - - while(curr != next) { - const auto lhs = copy[curr]; - const auto rhs = copy[next]; - (std::get *>(pools)->swap(lhs, rhs), ...); - copy[curr] = curr; - curr = next; - next = copy[curr]; + [this](auto *head, auto *...other) { + for(auto next = *length; next; --next) { + const auto pos = next - 1; + [[maybe_unused]] const auto entt = head->data()[pos]; + (other->swap_elements(other->data()[pos], entt), ...); } - } + }(std::get *>(pools)...); } private: - const typename basic_registry::size_type *length; - const std::tuple *..., pool_type *...> pools; + const std::tuple *..., storage_type *...> pools; + const size_type *const length; }; +} // namespace entt -} - - -#endif // ENTT_ENTITY_GROUP_HPP +#endif diff --git a/modules/entt/src/entt/entity/handle.hpp b/modules/entt/src/entt/entity/handle.hpp new file mode 100644 index 0000000..093d2af --- /dev/null +++ b/modules/entt/src/entt/entity/handle.hpp @@ -0,0 +1,340 @@ +#ifndef ENTT_ENTITY_HANDLE_HPP +#define ENTT_ENTITY_HANDLE_HPP + +#include +#include +#include +#include "../config/config.h" +#include "../core/type_traits.hpp" +#include "fwd.hpp" +#include "registry.hpp" + +namespace entt { + +/** + * @brief Non-owning handle to an entity. + * + * Tiny wrapper around a registry and an entity. + * + * @tparam Entity A valid entity type (see entt_traits for more details). + * @tparam Type Types to which to restrict the scope of a handle. + */ +template +struct basic_handle { + /*! @brief Type of registry accepted by the handle. */ + using registry_type = constness_as_t>, Entity>; + /*! @brief Underlying entity identifier. */ + using entity_type = typename registry_type::entity_type; + /*! @brief Underlying version type. */ + using version_type = typename registry_type::version_type; + /*! @brief Unsigned integer type. */ + using size_type = typename registry_type::size_type; + + /*! @brief Constructs an invalid handle. */ + basic_handle() ENTT_NOEXCEPT + : reg{}, + 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) ENTT_NOEXCEPT + : reg{&ref}, + entt{value} {} + + /** + * @brief Constructs a const handle from a non-const one. + * @tparam Other A valid entity type (see entt_traits for more details). + * @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 ENTT_NOEXCEPT { + static_assert(std::is_same_v || std::is_same_v, Entity>, "Invalid conversion between different handles"); + static_assert((sizeof...(Type) == 0 || ((sizeof...(Args) != 0 && sizeof...(Args) <= sizeof...(Type)) && ... && (type_list_contains_v, Args>))), "Invalid conversion between different handles"); + + return reg ? basic_handle{*reg, entt} : basic_handle{}; + } + + /** + * @brief Converts a handle to its underlying entity. + * @return The contained identifier. + */ + [[nodiscard]] operator entity_type() const ENTT_NOEXCEPT { + return entity(); + } + + /** + * @brief Checks if a handle refers to non-null registry pointer and entity. + * @return True if the handle refers to non-null registry and entity, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return reg && reg->valid(entt); + } + + /** + * @brief Checks if a handle refers to a valid entity or not. + * @return True if the handle refers to a valid entity, false otherwise. + */ + [[nodiscard]] bool valid() const { + return reg->valid(entt); + } + + /** + * @brief Returns a pointer to the underlying registry, if any. + * @return A pointer to the underlying registry, if any. + */ + [[nodiscard]] registry_type *registry() const ENTT_NOEXCEPT { + return reg; + } + + /** + * @brief Returns the entity associated with a handle. + * @return The entity associated with the handle. + */ + [[nodiscard]] entity_type entity() const ENTT_NOEXCEPT { + return entt; + } + + /** + * @brief Destroys the entity associated with a handle. + * @sa basic_registry::destroy + */ + void destroy() { + reg->destroy(entt); + } + + /** + * @brief Destroys the entity associated with a handle. + * @sa basic_registry::destroy + * @param version A desired version upon destruction. + */ + void destroy(const version_type version) { + reg->destroy(entt, version); + } + + /** + * @brief Assigns the given component to a handle. + * @sa basic_registry::emplace + * @tparam Component Type of component to create. + * @tparam Args Types of arguments to use to construct the component. + * @param args Parameters to use to initialize the component. + * @return A reference to the newly created component. + */ + template + decltype(auto) emplace(Args &&...args) const { + static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v), "Invalid type"); + return reg->template emplace(entt, std::forward(args)...); + } + + /** + * @brief Assigns or replaces the given component for a handle. + * @sa basic_registry::emplace_or_replace + * @tparam Component Type of component to assign or replace. + * @tparam Args Types of arguments to use to construct the component. + * @param args Parameters to use to initialize the component. + * @return A reference to the newly created component. + */ + template + decltype(auto) emplace_or_replace(Args &&...args) const { + static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v), "Invalid type"); + return reg->template emplace_or_replace(entt, std::forward(args)...); + } + + /** + * @brief Patches the given component for a handle. + * @sa basic_registry::patch + * @tparam Component Type of component to patch. + * @tparam Func Types of the function objects to invoke. + * @param func Valid function objects. + * @return A reference to the patched component. + */ + template + decltype(auto) patch(Func &&...func) const { + static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v), "Invalid type"); + return reg->template patch(entt, std::forward(func)...); + } + + /** + * @brief Replaces the given component for a handle. + * @sa basic_registry::replace + * @tparam Component Type of component to replace. + * @tparam Args Types of arguments to use to construct the component. + * @param args Parameters to use to initialize the component. + * @return A reference to the component being replaced. + */ + template + decltype(auto) replace(Args &&...args) const { + static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v), "Invalid type"); + return reg->template replace(entt, std::forward(args)...); + } + + /** + * @brief Removes the given components from a handle. + * @sa basic_registry::remove + * @tparam Component Types of components to remove. + * @return The number of components actually removed. + */ + template + size_type remove() const { + static_assert(sizeof...(Type) == 0 || (type_list_contains_v, Component> && ...), "Invalid type"); + return reg->template remove(entt); + } + + /** + * @brief Erases the given components from a handle. + * @sa basic_registry::erase + * @tparam Component Types of components to erase. + */ + template + void erase() const { + static_assert(sizeof...(Type) == 0 || (type_list_contains_v, Component> && ...), "Invalid type"); + reg->template erase(entt); + } + + /** + * @brief Checks if a handle has all the given components. + * @sa basic_registry::all_of + * @tparam Component Components for which to perform the check. + * @return True if the handle has all the components, false otherwise. + */ + template + [[nodiscard]] decltype(auto) all_of() const { + return reg->template all_of(entt); + } + + /** + * @brief Checks if a handle has at least one of the given components. + * @sa basic_registry::any_of + * @tparam Component Components for which to perform the check. + * @return True if the handle has at least one of the given components, + * false otherwise. + */ + template + [[nodiscard]] decltype(auto) any_of() const { + return reg->template any_of(entt); + } + + /** + * @brief Returns references to the given components for a handle. + * @sa basic_registry::get + * @tparam Component Types of components to get. + * @return References to the components owned by the handle. + */ + template + [[nodiscard]] decltype(auto) get() const { + static_assert(sizeof...(Type) == 0 || (type_list_contains_v, Component> && ...), "Invalid type"); + return reg->template get(entt); + } + + /** + * @brief Returns a reference to the given component for a handle. + * @sa basic_registry::get_or_emplace + * @tparam Component Type of component to get. + * @tparam Args Types of arguments to use to construct the component. + * @param args Parameters to use to initialize the component. + * @return Reference to the component owned by the handle. + */ + template + [[nodiscard]] decltype(auto) get_or_emplace(Args &&...args) const { + static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v), "Invalid type"); + return reg->template get_or_emplace(entt, std::forward(args)...); + } + + /** + * @brief Returns pointers to the given components for a handle. + * @sa basic_registry::try_get + * @tparam Component Types of components to get. + * @return Pointers to the components owned by the handle. + */ + template + [[nodiscard]] auto try_get() const { + static_assert(sizeof...(Type) == 0 || (type_list_contains_v, Component> && ...), "Invalid type"); + return reg->template try_get(entt); + } + + /** + * @brief Checks if a handle has components assigned. + * @return True if the handle has no components assigned, false otherwise. + */ + [[nodiscard]] bool orphan() const { + return reg->orphan(entt); + } + + /** + * @brief Visits a handle and returns the pools for its components. + * + * The signature of the function should be equivalent to the following: + * + * @code{.cpp} + * void(id_type, const basic_sparse_set &); + * @endcode + * + * Returned pools are those that contain the entity associated with the + * handle. + * + * @tparam Func Type of the function object to invoke. + * @param func A valid function object. + */ + template + void visit(Func &&func) const { + for(auto [id, storage]: reg->storage()) { + if(storage.contains(entt)) { + func(id, storage); + } + } + } + +private: + registry_type *reg; + entity_type entt; +}; + +/** + * @brief Compares two handles. + * @tparam Args Scope of the first handle. + * @tparam Other Scope of the second handle. + * @param lhs A valid handle. + * @param rhs 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 &lhs, const basic_handle &rhs) ENTT_NOEXCEPT { + return lhs.registry() == rhs.registry() && lhs.entity() == rhs.entity(); +} + +/** + * @brief Compares two handles. + * @tparam Args Scope of the first handle. + * @tparam Other Scope of the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return False if both handles refer to the same registry and the same + * entity, true otherwise. + */ +template +[[nodiscard]] bool operator!=(const basic_handle &lhs, const basic_handle &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Deduction guide. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +basic_handle(basic_registry &, Entity) -> basic_handle; + +/** + * @brief Deduction guide. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +basic_handle(const basic_registry &, Entity) -> basic_handle; + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/entity/helper.hpp b/modules/entt/src/entt/entity/helper.hpp index 29cd08a..2170ac2 100644 --- a/modules/entt/src/entt/entity/helper.hpp +++ b/modules/entt/src/entt/entity/helper.hpp @@ -1,26 +1,26 @@ #ifndef ENTT_ENTITY_HELPER_HPP #define ENTT_ENTITY_HELPER_HPP - #include #include "../config/config.h" -#include "../core/hashed_string.hpp" -#include "../signal/sigh.hpp" +#include "../core/fwd.hpp" +#include "../core/type_traits.hpp" +#include "../signal/delegate.hpp" +#include "fwd.hpp" #include "registry.hpp" - namespace entt { - /** * @brief Converts a registry to a view. - * @tparam Const Constness of the accepted registry. * @tparam Entity A valid entity type (see entt_traits for more details). */ -template +template struct as_view { + /*! @brief Underlying entity identifier. */ + using entity_type = std::remove_const_t; /*! @brief Type of registry to convert. */ - using registry_type = std::conditional_t, entt::basic_registry>; + using registry_type = constness_as_t, Entity>; /** * @brief Constructs a converter for a given registry. @@ -30,45 +30,43 @@ struct as_view { /** * @brief Conversion function from a registry to a view. + * @tparam Exclude Types of components used to filter the view. * @tparam Component Type of components used to construct the view. * @return A newly created view. */ - template - inline operator entt::basic_view() const { - return reg.template view(); + template + operator basic_view, Exclude>() const { + return reg.template view(Exclude{}); } private: registry_type ® }; - /** * @brief Deduction guide. - * - * It allows to deduce the constness of a registry directly from the instance - * provided to the constructor. - * * @tparam Entity A valid entity type (see entt_traits for more details). */ template -as_view(basic_registry &) ENTT_NOEXCEPT -> as_view; - +as_view(basic_registry &) -> as_view; -/*! @copydoc as_view */ +/** + * @brief Deduction guide. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ template -as_view(const basic_registry &) ENTT_NOEXCEPT -> as_view; - +as_view(const basic_registry &) -> as_view; /** * @brief Converts a registry to a group. - * @tparam Const Constness of the accepted registry. * @tparam Entity A valid entity type (see entt_traits for more details). */ -template +template struct as_group { + /*! @brief Underlying entity identifier. */ + using entity_type = std::remove_const_t; /*! @brief Type of registry to convert. */ - using registry_type = std::conditional_t, entt::basic_registry>; + using registry_type = constness_as_t, Entity>; /** * @brief Constructs a converter for a given registry. @@ -78,134 +76,81 @@ struct as_group { /** * @brief Conversion function from a registry to a group. - * - * @note - * Unfortunately, only full owning groups are supported because of an issue - * with msvc that doesn't manage to correctly deduce types. - * + * @tparam Get Types of components observed by the group. + * @tparam Exclude Types of components used to filter the group. * @tparam Owned Types of components owned by the group. * @return A newly created group. */ - template - inline operator entt::basic_group, Owned...>() const { - return reg.template group(); + template + operator basic_group, Get, Exclude>() const { + if constexpr(std::is_const_v) { + return reg.template group_if_exists(Get{}, Exclude{}); + } else { + return reg.template group(Get{}, Exclude{}); + } } private: registry_type ® }; - /** * @brief Deduction guide. - * - * It allows to deduce the constness of a registry directly from the instance - * provided to the constructor. - * * @tparam Entity A valid entity type (see entt_traits for more details). */ template -as_group(basic_registry &) ENTT_NOEXCEPT -> as_group; - - -/*! @copydoc as_group */ -template -as_group(const basic_registry &) ENTT_NOEXCEPT -> as_group; - +as_group(basic_registry &) -> as_group; /** - * @brief Dependency function prototype. - * - * A _dependency function_ is a built-in listener to use to automatically assign - * components to an entity when a type has a dependency on some other types. - * - * This is a prototype function to use to create dependencies.
- * It isn't intended for direct use, although nothing forbids using it freely. - * + * @brief Deduction guide. * @tparam Entity A valid entity type (see entt_traits for more details). - * @tparam Component Type of component that triggers the dependency handler. - * @tparam Dependency Types of components to assign to an entity if triggered. - * @param reg A valid reference to a registry. - * @param entt A valid entity identifier. */ -template -void dependency(basic_registry ®, const Entity entt, const Component &) { - ((reg.template has(entt) ? void() : (reg.template assign(entt), void())), ...); -} - +template +as_group(const basic_registry &) -> as_group; /** - * @brief Connects a dependency function to the given sink. - * - * A _dependency function_ is a built-in listener to use to automatically assign - * components to an entity when a type has a dependency on some other types. - * - * The following adds components `a_type` and `another_type` whenever `my_type` - * is assigned to an entity: - * @code{.cpp} - * entt::registry registry; - * entt::connect(registry.construction()); - * @endcode - * - * @tparam Dependency Types of components to assign to an entity if triggered. - * @tparam Component Type of component that triggers the dependency handler. + * @brief Helper to create a listener that directly invokes a member function. + * @tparam Member Member function to invoke on a component of the given type. * @tparam Entity A valid entity type (see entt_traits for more details). - * @param sink A sink object properly initialized. + * @param reg A registry that contains the given entity and its components. + * @param entt Entity from which to get the component. */ -template -inline void connect(sink &, const Entity, Component &)> sink) { - sink.template connect>(); +template +void invoke(basic_registry ®, const Entity entt) { + static_assert(std::is_member_function_pointer_v, "Invalid pointer to non-static member function"); + delegate &, const Entity)> func; + func.template connect(reg.template get>(entt)); + func(reg, entt); } - /** - * @brief Disconnects a dependency function from the given sink. - * - * A _dependency function_ is a built-in listener to use to automatically assign - * components to an entity when a type has a dependency on some other types. + * @brief Returns the entity associated with a given component. * - * The following breaks the dependency between the component `my_type` and the - * components `a_type` and `another_type`: - * @code{.cpp} - * entt::registry registry; - * entt::disconnect(registry.construction()); - * @endcode + * @warning + * Currently, this function only works correctly with the default pool as it + * makes assumptions about how the components are laid out. * - * @tparam Dependency Types of components used to create the dependency. - * @tparam Component Type of component that triggers the dependency handler. * @tparam Entity A valid entity type (see entt_traits for more details). - * @param sink A sink object properly initialized. + * @tparam Component Type of component. + * @param reg A registry that contains the given entity and its components. + * @param instance A valid component instance. + * @return The entity associated with the given component. */ -template -inline void disconnect(sink &, const Entity, Component &)> sink) { - sink.template disconnect>(); -} - - -/** - * @brief Alias template to ease the assignment of tags to entities. - * - * If used in combination with hashed strings, it simplifies the assignment of - * tags to entities and the use of tags in general where a type would be - * required otherwise.
- * As an example and where the user defined literal for hashed strings hasn't - * been changed: - * @code{.cpp} - * entt::registry registry; - * registry.assign>(entity); - * @endcode - * - * @note - * Tags are empty components and therefore candidates for the empty component - * optimization. - * - * @tparam Value The numeric representation of an instance of hashed string. - */ -template -using tag = std::integral_constant; - +template +Entity to_entity(const basic_registry ®, const Component &instance) { + const auto &storage = reg.template storage(); + const typename basic_registry::base_type &base = storage; + const auto *addr = std::addressof(instance); + + for(auto it = base.rbegin(), last = base.rend(); it < last; it += ENTT_PACKED_PAGE) { + if(const auto dist = (addr - std::addressof(storage.get(*it))); dist >= 0 && dist < ENTT_PACKED_PAGE) { + return *(it + dist); + } + } + return null; } +} // namespace entt -#endif // ENTT_ENTITY_HELPER_HPP +#endif diff --git a/modules/entt/src/entt/entity/observer.hpp b/modules/entt/src/entt/entity/observer.hpp new file mode 100644 index 0000000..ebcd5b4 --- /dev/null +++ b/modules/entt/src/entt/entity/observer.hpp @@ -0,0 +1,436 @@ +#ifndef ENTT_ENTITY_OBSERVER_HPP +#define ENTT_ENTITY_OBSERVER_HPP + +#include +#include +#include +#include +#include +#include "../config/config.h" +#include "../core/type_traits.hpp" +#include "../signal/delegate.hpp" +#include "entity.hpp" +#include "fwd.hpp" +#include "registry.hpp" +#include "storage.hpp" +#include "utility.hpp" + +namespace entt { + +/*! @brief Grouping matcher. */ +template +struct matcher {}; + +/** + * @brief Collector. + * + * Primary template isn't defined on purpose. All the specializations give a + * compile-time error, but for a few reasonable cases. + */ +template +struct basic_collector; + +/** + * @brief Collector. + * + * A collector contains a set of rules (literally, matchers) to use to track + * entities.
+ * Its main purpose is to generate a descriptor that allows an observer to know + * how to connect to a registry. + */ +template<> +struct basic_collector<> { + /** + * @brief Adds a grouping matcher to the collector. + * @tparam AllOf Types of components tracked by the matcher. + * @tparam NoneOf Types of components used to filter out entities. + * @return The updated collector. + */ + template + static constexpr auto group(exclude_t = {}) ENTT_NOEXCEPT { + return basic_collector, type_list<>, type_list, AllOf...>>{}; + } + + /** + * @brief Adds an observing matcher to the collector. + * @tparam AnyOf Type of component for which changes should be detected. + * @return The updated collector. + */ + template + static constexpr auto update() ENTT_NOEXCEPT { + return basic_collector, type_list<>, AnyOf>>{}; + } +}; + +/** + * @brief Collector. + * @copydetails basic_collector<> + * @tparam Reject Untracked types used to filter out entities. + * @tparam Require Untracked types required by the matcher. + * @tparam Rule Specific details of the current matcher. + * @tparam Other Other matchers. + */ +template +struct basic_collector, type_list, Rule...>, Other...> { + /*! @brief Current matcher. */ + using current_type = matcher, type_list, Rule...>; + + /** + * @brief Adds a grouping matcher to the collector. + * @tparam AllOf Types of components tracked by the matcher. + * @tparam NoneOf Types of components used to filter out entities. + * @return The updated collector. + */ + template + static constexpr auto group(exclude_t = {}) ENTT_NOEXCEPT { + return basic_collector, type_list<>, type_list, AllOf...>, current_type, Other...>{}; + } + + /** + * @brief Adds an observing matcher to the collector. + * @tparam AnyOf Type of component for which changes should be detected. + * @return The updated collector. + */ + template + static constexpr auto update() ENTT_NOEXCEPT { + return basic_collector, type_list<>, AnyOf>, current_type, Other...>{}; + } + + /** + * @brief Updates the filter of the last added matcher. + * @tparam AllOf Types of components required by the matcher. + * @tparam NoneOf Types of components used to filter out entities. + * @return The updated collector. + */ + template + static constexpr auto where(exclude_t = {}) ENTT_NOEXCEPT { + using extended_type = matcher, type_list, Rule...>; + return basic_collector{}; + } +}; + +/*! @brief Variable template used to ease the definition of collectors. */ +inline constexpr basic_collector<> collector{}; + +/** + * @brief Observer. + * + * An observer returns all the entities and only the entities that fit the + * requirements of at least one matcher. Moreover, it's guaranteed that the + * entity list is tightly packed in memory for fast iterations.
+ * In general, observers don't stay true to the order of any set of components. + * + * Observers work mainly with two types of matchers, provided through a + * collector: + * + * * Observing matcher: an observer will return at least all the living entities + * for which one or more of the given components have been updated and not yet + * destroyed. + * * Grouping matcher: an observer will return at least all the living entities + * that would have entered the given group if it existed and that would have + * not yet left it. + * + * If an entity respects the requirements of multiple matchers, it will be + * returned once and only once by the observer in any case. + * + * Matchers support also filtering by means of a _where_ clause that accepts + * both a list of types and an exclusion list.
+ * Whenever a matcher finds that an entity matches its requirements, the + * condition of the filter is verified before to register the entity itself. + * Moreover, a registered entity isn't returned by the observer if the condition + * set by the filter is broken in the meantime. + * + * @b Important + * + * Iterators aren't invalidated if: + * + * * New instances of the given components are created and assigned to entities. + * * The entity currently pointed is modified (as an example, if one of the + * given components is removed from the entity to which the iterator points). + * * The entity currently pointed is destroyed. + * + * In all the other cases, modifying the pools of the given components in any + * way invalidates all the iterators and using them results in undefined + * behavior. + * + * @warning + * Lifetime of an observer doesn't necessarily have to overcome that of the + * registry to which it is connected. However, the observer must be disconnected + * from the registry before being destroyed to avoid crashes due to dangling + * pointers. + * + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +class basic_observer { + using payload_type = std::uint32_t; + + template + struct matcher_handler; + + template + struct matcher_handler, type_list, AnyOf>> { + template + static void maybe_valid_if(basic_observer &obs, basic_registry ®, const Entity entt) { + if(reg.template all_of(entt) && !reg.template any_of(entt)) { + if(!obs.storage.contains(entt)) { + obs.storage.emplace(entt); + } + + obs.storage.get(entt) |= (1 << Index); + } + } + + template + static void discard_if(basic_observer &obs, basic_registry &, const Entity entt) { + if(obs.storage.contains(entt) && !(obs.storage.get(entt) &= (~(1 << Index)))) { + obs.storage.erase(entt); + } + } + + template + static void connect(basic_observer &obs, basic_registry ®) { + (reg.template on_destroy().template connect<&discard_if>(obs), ...); + (reg.template on_construct().template connect<&discard_if>(obs), ...); + reg.template on_update().template connect<&maybe_valid_if>(obs); + reg.template on_destroy().template connect<&discard_if>(obs); + } + + static void disconnect(basic_observer &obs, basic_registry ®) { + (reg.template on_destroy().disconnect(obs), ...); + (reg.template on_construct().disconnect(obs), ...); + reg.template on_update().disconnect(obs); + reg.template on_destroy().disconnect(obs); + } + }; + + template + struct matcher_handler, type_list, type_list, AllOf...>> { + template + static void maybe_valid_if(basic_observer &obs, basic_registry ®, const Entity entt) { + auto condition = [®, entt]() { + if constexpr(sizeof...(Ignore) == 0) { + return reg.template all_of(entt) && !reg.template any_of(entt); + } else { + return reg.template all_of(entt) && ((std::is_same_v || !reg.template any_of(entt)) && ...) && !reg.template any_of(entt); + } + }; + + if(condition()) { + if(!obs.storage.contains(entt)) { + obs.storage.emplace(entt); + } + + obs.storage.get(entt) |= (1 << Index); + } + } + + template + static void discard_if(basic_observer &obs, basic_registry &, const Entity entt) { + if(obs.storage.contains(entt) && !(obs.storage.get(entt) &= (~(1 << Index)))) { + obs.storage.erase(entt); + } + } + + template + static void connect(basic_observer &obs, basic_registry ®) { + (reg.template on_destroy().template connect<&discard_if>(obs), ...); + (reg.template on_construct().template connect<&discard_if>(obs), ...); + (reg.template on_construct().template connect<&maybe_valid_if>(obs), ...); + (reg.template on_destroy().template connect<&maybe_valid_if>(obs), ...); + (reg.template on_destroy().template connect<&discard_if>(obs), ...); + (reg.template on_construct().template connect<&discard_if>(obs), ...); + } + + static void disconnect(basic_observer &obs, basic_registry ®) { + (reg.template on_destroy().disconnect(obs), ...); + (reg.template on_construct().disconnect(obs), ...); + (reg.template on_construct().disconnect(obs), ...); + (reg.template on_destroy().disconnect(obs), ...); + (reg.template on_destroy().disconnect(obs), ...); + (reg.template on_construct().disconnect(obs), ...); + } + }; + + template + static void disconnect(basic_registry ®, basic_observer &obs) { + (matcher_handler::disconnect(obs, reg), ...); + } + + template + void connect(basic_registry ®, std::index_sequence) { + static_assert(sizeof...(Matcher) < std::numeric_limits::digits, "Too many matchers"); + (matcher_handler::template connect(*this, reg), ...); + release.template connect<&basic_observer::disconnect>(reg); + } + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Random access iterator type. */ + using iterator = typename basic_sparse_set::iterator; + + /*! @brief Default constructor. */ + basic_observer() + : release{}, + storage{} {} + + /*! @brief Default copy constructor, deleted on purpose. */ + basic_observer(const basic_observer &) = delete; + /*! @brief Default move constructor, deleted on purpose. */ + basic_observer(basic_observer &&) = delete; + + /** + * @brief Creates an observer and connects it to a given registry. + * @tparam Matcher Types of matchers to use to initialize the observer. + * @param reg A valid reference to a registry. + */ + template + basic_observer(basic_registry ®, basic_collector) + : basic_observer{} { + connect(reg, std::index_sequence_for{}); + } + + /*! @brief Default destructor. */ + ~basic_observer() = default; + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This observer. + */ + basic_observer &operator=(const basic_observer &) = delete; + + /** + * @brief Default move assignment operator, deleted on purpose. + * @return This observer. + */ + basic_observer &operator=(basic_observer &&) = delete; + + /** + * @brief Connects an observer to a given registry. + * @tparam Matcher Types of matchers to use to initialize the observer. + * @param reg A valid reference to a registry. + */ + template + void connect(basic_registry ®, basic_collector) { + disconnect(); + connect(reg, std::index_sequence_for{}); + storage.clear(); + } + + /*! @brief Disconnects an observer from the registry it keeps track of. */ + void disconnect() { + if(release) { + release(*this); + release.reset(); + } + } + + /** + * @brief Returns the number of elements in an observer. + * @return Number of elements. + */ + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return storage.size(); + } + + /** + * @brief Checks whether an observer is empty. + * @return True if the observer is empty, false otherwise. + */ + [[nodiscard]] bool empty() const ENTT_NOEXCEPT { + return storage.empty(); + } + + /** + * @brief Direct access to the list of entities of the observer. + * + * The returned pointer is such that range `[data(), data() + size())` is + * always a valid range, even if the container is empty. + * + * @note + * Entities are in the reverse order as returned by the `begin`/`end` + * iterators. + * + * @return A pointer to the array of entities. + */ + [[nodiscard]] const entity_type *data() const ENTT_NOEXCEPT { + return storage.data(); + } + + /** + * @brief Returns an iterator to the first entity of the observer. + * + * The returned iterator points to the first entity of the observer. If the + * container is empty, the returned iterator will be equal to `end()`. + * + * @return An iterator to the first entity of the observer. + */ + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return storage.basic_sparse_set::begin(); + } + + /** + * @brief Returns an iterator that is past the last entity of the observer. + * + * The returned iterator points to the entity following the last entity of + * the observer. Attempting to dereference the returned iterator results in + * undefined behavior. + * + * @return An iterator to the entity following the last entity of the + * observer. + */ + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return storage.basic_sparse_set::end(); + } + + /*! @brief Clears the underlying container. */ + void clear() ENTT_NOEXCEPT { + storage.clear(); + } + + /** + * @brief Iterates entities and applies the given function object to them. + * + * The function object is invoked for each entity.
+ * The signature of the function must be equivalent to the following form: + * + * @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); + } + } + + /** + * @brief Iterates entities and applies the given function object to them, + * then clears the observer. + * + * @sa each + * + * @tparam Func Type of the function object to invoke. + * @param func A valid function object. + */ + template + void each(Func func) { + std::as_const(*this).each(std::move(func)); + clear(); + } + +private: + delegate release; + basic_storage storage; +}; + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/entity/organizer.hpp b/modules/entt/src/entt/entity/organizer.hpp new file mode 100644 index 0000000..648ad05 --- /dev/null +++ b/modules/entt/src/entt/entity/organizer.hpp @@ -0,0 +1,484 @@ +#ifndef ENTT_ENTITY_ORGANIZER_HPP +#define ENTT_ENTITY_ORGANIZER_HPP + +#include +#include +#include +#include +#include +#include "../container/dense_map.hpp" +#include "../core/type_info.hpp" +#include "../core/type_traits.hpp" +#include "../core/utility.hpp" +#include "fwd.hpp" +#include "helper.hpp" + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct is_view: std::false_type {}; + +template +struct is_view, exclude_t>>: std::true_type {}; + +template +inline constexpr bool is_view_v = is_view::value; + +template +struct unpack_type { + using ro = std::conditional_t< + type_list_contains_v> || (std::is_const_v && !type_list_contains_v>), + type_list>, + type_list<>>; + + using rw = std::conditional_t< + type_list_contains_v> || (!std::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>::ro...>; + using rw = type_list_cat_t>::rw...>; +}; + +template +struct unpack_type, exclude_t>, type_list> + : unpack_type, 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...>; +}; + +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 + +/** + * Internal details not to be documented. + * @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 Entity A valid entity type (see entt_traits for more details). + */ +template +class basic_organizer final { + using callback_type = void(const void *, basic_registry &); + using prepare_type = void(basic_registry &); + using dependency_type = std::size_t(const bool, const type_info **, const std::size_t); + + struct vertex_data final { + std::size_t ro_count{}; + std::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(basic_registry ®) { + if constexpr(std::is_same_v>) { + return reg; + } else if constexpr(internal::is_view_v) { + return as_view{reg}; + } else { + return reg.ctx().template emplace>(); + } + } + + template + [[nodiscard]] static auto to_args(basic_registry ®, type_list) { + return std::tuple(reg))...>(extract(reg)...); + } + + template + static std::size_t fill_dependencies(type_list, [[maybe_unused]] const type_info **buffer, [[maybe_unused]] const std::size_t count) { + if constexpr(sizeof...(Type) == 0u) { + return {}; + } else { + const type_info *info[sizeof...(Type)]{&type_id()...}; + const auto length = (std::min)(count, sizeof...(Type)); + std::copy_n(info, length, buffer); + return length; + } + } + + template + void track_dependencies(std::size_t index, const bool requires_registry, type_list, type_list) { + dependencies[type_hash>::value()].emplace_back(index, requires_registry || (sizeof...(RO) + sizeof...(RW) == 0u)); + (dependencies[type_hash::value()].emplace_back(index, false), ...); + (dependencies[type_hash::value()].emplace_back(index, true), ...); + } + + [[nodiscard]] std::vector adjacency_matrix() { + const auto length = vertices.size(); + std::vector edges(length * length, false); + + // creates the adjacency matrix + for(const auto &deps: dependencies) { + const auto last = deps.second.cend(); + auto it = deps.second.cbegin(); + + while(it != last) { + if(it->second) { + // rw item + if(auto curr = it++; it != last) { + if(it->second) { + edges[curr->first * length + it->first] = true; + } else { + if(const auto next = std::find_if(it, last, [](const auto &elem) { return elem.second; }); next != last) { + for(; it != next; ++it) { + edges[curr->first * length + it->first] = true; + edges[it->first * length + next->first] = true; + } + } else { + for(; it != next; ++it) { + edges[curr->first * length + it->first] = true; + } + } + } + } + } else { + // ro item, possibly only on first iteration + if(const auto next = std::find_if(it, last, [](const auto &elem) { return elem.second; }); next != last) { + for(; it != next; ++it) { + edges[it->first * length + next->first] = true; + } + } else { + it = last; + } + } + } + } + + // computes the transitive closure + for(std::size_t vk{}; vk < length; ++vk) { + for(std::size_t vi{}; vi < length; ++vi) { + for(std::size_t vj{}; vj < length; ++vj) { + edges[vi * length + vj] = edges[vi * length + vj] || (edges[vi * length + vk] && edges[vk * length + vj]); + } + } + } + + // applies the transitive reduction + for(std::size_t vert{}; vert < length; ++vert) { + edges[vert * length + vert] = false; + } + + for(std::size_t vj{}; vj < length; ++vj) { + for(std::size_t vi{}; vi < length; ++vi) { + if(edges[vi * length + vj]) { + for(std::size_t vk{}; vk < length; ++vk) { + if(edges[vj * length + vk]) { + edges[vi * length + vk] = false; + } + } + } + } + } + + return edges; + } + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::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 vtype True if the vertex is a top-level one, false otherwise. + * @param data The data associated with the vertex. + * @param edges The indices of the children in the adjacency list. + */ + vertex(const bool vtype, vertex_data data, std::vector edges) + : is_top_level{vtype}, + node{std::move(data)}, + reachable{std::move(edges)} {} + + /** + * @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. + */ + size_type ro_dependency(const type_info **buffer, const std::size_t length) const ENTT_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. + */ + size_type rw_dependency(const type_info **buffer, const std::size_t length) const ENTT_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. + */ + size_type ro_count() const ENTT_NOEXCEPT { + return node.ro_count; + } + + /** + * @brief Returns the number of writable resources of a vertex. + * @return The number of writable resources of the vertex. + */ + size_type rw_count() const ENTT_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. + */ + bool top_level() const ENTT_NOEXCEPT { + return is_top_level; + } + + /** + * @brief Returns a type info object associated with a vertex. + * @return A properly initialized type info object. + */ + const type_info &info() const ENTT_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. + */ + const char *name() const ENTT_NOEXCEPT { + return node.name; + } + + /** + * @brief Returns the function associated with a vertex. + * @return The function associated with the vertex. + */ + function_type *callback() const ENTT_NOEXCEPT { + return node.callback; + } + + /** + * @brief Returns the payload associated with a vertex, if any. + * @return The payload associated with the vertex, if any. + */ + const void *data() const ENTT_NOEXCEPT { + return node.payload; + } + + /** + * @brief Returns the list of nodes reachable from a given vertex. + * @return The list of nodes reachable from the vertex. + */ + const std::vector &children() const ENTT_NOEXCEPT { + return reachable; + } + + /** + * @brief Prepares a registry and assures that all required resources + * are properly instantiated before using them. + * @param reg A valid registry. + */ + void prepare(basic_registry ®) const { + node.prepare ? node.prepare(reg) : void(); + } + + private: + bool is_top_level; + vertex_data node; + std::vector reachable; + }; + + /** + * @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)); + constexpr auto requires_registry = type_list_contains_v>; + + callback_type *callback = +[](const void *, basic_registry ®) { + std::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 std::size_t length) { return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length); }, + +[](basic_registry ®) { void(to_args(reg, typename resource_type::args{})); }, + &type_id>()}; + + track_dependencies(vertices.size(), requires_registry, typename resource_type::ro{}, typename resource_type::rw{}); + vertices.push_back(std::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)); + constexpr auto requires_registry = type_list_contains_v>; + + callback_type *callback = +[](const void *payload, basic_registry ®) { + Type *curr = static_cast(const_cast *>(payload)); + std::apply(Candidate, std::tuple_cat(std::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 std::size_t length) { return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length); }, + +[](basic_registry ®) { void(to_args(reg, typename resource_type::args{})); }, + &type_id>()}; + + track_dependencies(vertices.size(), requires_registry, typename resource_type::ro{}, typename resource_type::rw{}); + vertices.push_back(std::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 std::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(std::move(vdata)); + } + + /** + * @brief Generates a task graph for the current content. + * @return The adjacency list of the task graph. + */ + std::vector graph() { + const auto edges = adjacency_matrix(); + + // creates the adjacency list + std::vector adjacency_list{}; + adjacency_list.reserve(vertices.size()); + + for(std::size_t col{}, length = vertices.size(); col < length; ++col) { + std::vector reachable{}; + const auto row = col * length; + bool is_top_level = true; + + for(std::size_t next{}; next < length; ++next) { + if(edges[row + next]) { + reachable.push_back(next); + } + } + + for(std::size_t next{}; next < length && is_top_level; ++next) { + is_top_level = !edges[next * length + col]; + } + + adjacency_list.emplace_back(is_top_level, vertices[col], std::move(reachable)); + } + + return adjacency_list; + } + + /*! @brief Erases all elements from a container. */ + void clear() { + dependencies.clear(); + vertices.clear(); + } + +private: + dense_map>, identity> dependencies; + std::vector vertices; +}; + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/entity/prototype.hpp b/modules/entt/src/entt/entity/prototype.hpp deleted file mode 100644 index 5adef9f..0000000 --- a/modules/entt/src/entt/entity/prototype.hpp +++ /dev/null @@ -1,483 +0,0 @@ -#ifndef ENTT_ENTITY_PROTOTYPE_HPP -#define ENTT_ENTITY_PROTOTYPE_HPP - - -#include -#include -#include -#include -#include -#include "../config/config.h" -#include "registry.hpp" -#include "entity.hpp" -#include "fwd.hpp" - - -namespace entt { - - -/** - * @brief Prototype container for _concepts_. - * - * A prototype is used to define a _concept_ in terms of components.
- * Prototypes act as templates for those specific types of an application which - * users would otherwise define through a series of component assignments to - * entities. In other words, prototypes can be used to assign components to - * entities of a registry at once. - * - * @note - * Components used along with prototypes must be copy constructible. Prototypes - * wrap component types with custom types, so they do not interfere with other - * users of the registry they were built with. - * - * @warning - * Prototypes directly use their underlying registries to store entities and - * components for their purposes. Users must ensure that the lifetime of a - * registry and its contents exceed that of the prototypes that use it. - * - * @tparam Entity A valid entity type (see entt_traits for more details). - */ -template -class basic_prototype { - using basic_fn_type = void(const basic_prototype &, basic_registry &, const Entity); - using component_type = typename basic_registry::component_type; - - template - struct component_wrapper { Component component; }; - - struct component_handler { - basic_fn_type *assign_or_replace; - basic_fn_type *assign; - }; - - void release() { - if(reg->valid(entity)) { - reg->destroy(entity); - } - } - -public: - /*! @brief Registry type. */ - using registry_type = basic_registry; - /*! @brief Underlying entity identifier. */ - using entity_type = Entity; - /*! @brief Unsigned integer type. */ - using size_type = std::size_t; - - /** - * @brief Constructs a prototype that is bound to a given registry. - * @param ref A valid reference to a registry. - */ - basic_prototype(registry_type &ref) - : reg{&ref}, - entity{ref.create()} - {} - - /** - * @brief Releases all its resources. - */ - ~basic_prototype() { - release(); - } - - /** - * @brief Move constructor. - * - * After prototype move construction, instances that have been moved from - * are placed in a valid but unspecified state. It's highly discouraged to - * continue using them. - * - * @param other The instance to move from. - */ - basic_prototype(basic_prototype &&other) - : handlers{std::move(other.handlers)}, - reg{other.reg}, - entity{other.entity} - { - other.entity = null; - } - - /** - * @brief Move assignment operator. - * - * After prototype move assignment, instances that have been moved from are - * placed in a valid but unspecified state. It's highly discouraged to - * continue using them. - * - * @param other The instance to move from. - * @return This prototype. - */ - basic_prototype & operator=(basic_prototype &&other) { - if(this != &other) { - auto tmp{std::move(other)}; - handlers.swap(tmp.handlers); - std::swap(reg, tmp.reg); - std::swap(entity, tmp.entity); - } - - return *this; - } - - /** - * @brief Assigns to or replaces the given component of a prototype. - * @tparam Component Type of component to assign or replace. - * @tparam Args Types of arguments to use to construct the component. - * @param args Parameters to use to initialize the component. - * @return A reference to the newly created component. - */ - template - Component & set(Args &&... args) { - component_handler handler; - - handler.assign_or_replace = [](const basic_prototype &proto, registry_type &other, const Entity dst) { - const auto &wrapper = proto.reg->template get>(proto.entity); - other.template assign_or_replace(dst, wrapper.component); - }; - - handler.assign = [](const basic_prototype &proto, registry_type &other, const Entity dst) { - if(!other.template has(dst)) { - const auto &wrapper = proto.reg->template get>(proto.entity); - other.template assign(dst, wrapper.component); - } - }; - - handlers[reg->template type()] = handler; - auto &wrapper = reg->template assign_or_replace>(entity, Component{std::forward(args)...}); - return wrapper.component; - } - - /** - * @brief Removes the given component from a prototype. - * @tparam Component Type of component to remove. - */ - template - void unset() ENTT_NOEXCEPT { - reg->template reset>(entity); - handlers.erase(reg->template type()); - } - - /** - * @brief Checks if a prototype owns all the given components. - * @tparam Component Components for which to perform the check. - * @return True if the prototype owns all the components, false otherwise. - */ - template - bool has() const ENTT_NOEXCEPT { - return reg->template has...>(entity); - } - - /** - * @brief Returns references to the given components. - * - * @warning - * Attempting to get a component from a prototype that doesn't own it - * results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * prototype doesn't own an instance of the given component. - * - * @tparam Component Types of components to get. - * @return References to the components owned by the prototype. - */ - template - decltype(auto) get() const ENTT_NOEXCEPT { - if constexpr(sizeof...(Component) == 1) { - return (std::as_const(*reg).template get>(entity).component); - } else { - return std::tuple &...>{get()...}; - } - } - - /*! @copydoc get */ - template - inline decltype(auto) get() ENTT_NOEXCEPT { - if constexpr(sizeof...(Component) == 1) { - return (const_cast(std::as_const(*this).template get()), ...); - } else { - return std::tuple{get()...}; - } - } - - /** - * @brief Returns pointers to the given components. - * @tparam Component Types of components to get. - * @return Pointers to the components owned by the prototype. - */ - template - auto try_get() const ENTT_NOEXCEPT { - if constexpr(sizeof...(Component) == 1) { - const auto *wrapper = reg->template try_get>(entity); - return wrapper ? &wrapper->component : nullptr; - } else { - return std::tuple *...>{try_get()...}; - } - } - - /*! @copydoc try_get */ - template - inline auto try_get() ENTT_NOEXCEPT { - if constexpr(sizeof...(Component) == 1) { - return (const_cast(std::as_const(*this).template try_get()), ...); - } else { - return std::tuple{try_get()...}; - } - } - - /** - * @brief Creates a new entity using a given prototype. - * - * Utility shortcut, equivalent to the following snippet: - * - * @code{.cpp} - * const auto entity = registry.create(); - * prototype(registry, entity); - * @endcode - * - * @note - * The registry may or may not be different from the one already used by - * the prototype. There is also an overload that directly uses the - * underlying registry. - * - * @param other A valid reference to a registry. - * @return A valid entity identifier. - */ - entity_type create(registry_type &other) const { - const auto entt = other.create(); - assign(other, entt); - return entt; - } - - /** - * @brief Creates a new entity using a given prototype. - * - * Utility shortcut, equivalent to the following snippet: - * - * @code{.cpp} - * const auto entity = registry.create(); - * prototype(entity); - * @endcode - * - * @note - * This overload directly uses the underlying registry as a working space. - * Therefore, the components of the prototype and of the entity will share - * the same registry. - * - * @return A valid entity identifier. - */ - inline entity_type create() const { - return create(*reg); - } - - /** - * @brief Assigns the components of a prototype to a given entity. - * - * Assigning a prototype to an entity won't overwrite existing components - * under any circumstances.
- * In other words, only those components that the entity doesn't own yet are - * copied over. All the other components remain unchanged. - * - * @note - * The registry may or may not be different from the one already used by - * the prototype. There is also an overload that directly uses the - * underlying registry. - * - * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. - * - * @param other A valid reference to a registry. - * @param dst A valid entity identifier. - */ - void assign(registry_type &other, const entity_type dst) const { - for(auto &handler: handlers) { - handler.second.assign(*this, other, dst); - } - } - - /** - * @brief Assigns the components of a prototype to a given entity. - * - * Assigning a prototype to an entity won't overwrite existing components - * under any circumstances.
- * In other words, only those components that the entity doesn't own yet are - * copied over. All the other components remain unchanged. - * - * @note - * This overload directly uses the underlying registry as a working space. - * Therefore, the components of the prototype and of the entity will share - * the same registry. - * - * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. - * - * @param dst A valid entity identifier. - */ - inline void assign(const entity_type dst) const { - assign(*reg, dst); - } - - /** - * @brief Assigns or replaces the components of a prototype for an entity. - * - * Existing components are overwritten, if any. All the other components - * will be copied over to the target entity. - * - * @note - * The registry may or may not be different from the one already used by - * the prototype. There is also an overload that directly uses the - * underlying registry. - * - * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. - * - * @param other A valid reference to a registry. - * @param dst A valid entity identifier. - */ - void assign_or_replace(registry_type &other, const entity_type dst) const { - for(auto &handler: handlers) { - handler.second.assign_or_replace(*this, other, dst); - } - } - - /** - * @brief Assigns or replaces the components of a prototype for an entity. - * - * Existing components are overwritten, if any. All the other components - * will be copied over to the target entity. - * - * @note - * This overload directly uses the underlying registry as a working space. - * Therefore, the components of the prototype and of the entity will share - * the same registry. - * - * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. - * - * @param dst A valid entity identifier. - */ - inline void assign_or_replace(const entity_type dst) const { - assign_or_replace(*reg, dst); - } - - /** - * @brief Assigns the components of a prototype to an entity. - * - * Assigning a prototype to an entity won't overwrite existing components - * under any circumstances.
- * In other words, only the components that the entity doesn't own yet are - * copied over. All the other components remain unchanged. - * - * @note - * The registry may or may not be different from the one already used by - * the prototype. There is also an overload that directly uses the - * underlying registry. - * - * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. - * - * @param other A valid reference to a registry. - * @param dst A valid entity identifier. - */ - inline void operator()(registry_type &other, const entity_type dst) const ENTT_NOEXCEPT { - assign(other, dst); - } - - /** - * @brief Assigns the components of a prototype to an entity. - * - * Assigning a prototype to an entity won't overwrite existing components - * under any circumstances.
- * In other words, only the components that the entity doesn't own yet are - * copied over. All the other components remain unchanged. - * - * @note - * This overload directly uses the underlying registry as a working space. - * Therefore, the components of the prototype and of the entity will share - * the same registry. - * - * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. - * - * @param dst A valid entity identifier. - */ - inline void operator()(const entity_type dst) const ENTT_NOEXCEPT { - assign(*reg, dst); - } - - /** - * @brief Creates a new entity using a given prototype. - * - * Utility shortcut, equivalent to the following snippet: - * - * @code{.cpp} - * const auto entity = registry.create(); - * prototype(registry, entity); - * @endcode - * - * @note - * The registry may or may not be different from the one already used by - * the prototype. There is also an overload that directly uses the - * underlying registry. - * - * @param other A valid reference to a registry. - * @return A valid entity identifier. - */ - inline entity_type operator()(registry_type &other) const ENTT_NOEXCEPT { - return create(other); - } - - /** - * @brief Creates a new entity using a given prototype. - * - * Utility shortcut, equivalent to the following snippet: - * - * @code{.cpp} - * const auto entity = registry.create(); - * prototype(entity); - * @endcode - * - * @note - * This overload directly uses the underlying registry as a working space. - * Therefore, the components of the prototype and of the entity will share - * the same registry. - * - * @return A valid entity identifier. - */ - inline entity_type operator()() const ENTT_NOEXCEPT { - return create(*reg); - } - - /** - * @brief Returns a reference to the underlying registry. - * @return A reference to the underlying registry. - */ - inline const registry_type & backend() const ENTT_NOEXCEPT { - return *reg; - } - - /*! @copydoc backend */ - inline registry_type & backend() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).backend()); - } - -private: - std::unordered_map handlers; - registry_type *reg; - entity_type entity; -}; - - -} - - -#endif // ENTT_ENTITY_PROTOTYPE_HPP diff --git a/modules/entt/src/entt/entity/registry.hpp b/modules/entt/src/entt/entity/registry.hpp index 6732393..b7d773a 100644 --- a/modules/entt/src/entt/entity/registry.hpp +++ b/modules/entt/src/entt/entity/registry.hpp @@ -1,1313 +1,1254 @@ #ifndef ENTT_ENTITY_REGISTRY_HPP #define ENTT_ENTITY_REGISTRY_HPP - -#include -#include -#include -#include +#include #include -#include #include -#include +#include +#include #include +#include +#include #include "../config/config.h" -#include "../core/family.hpp" +#include "../container/dense_map.hpp" #include "../core/algorithm.hpp" -#include "../core/hashed_string.hpp" +#include "../core/any.hpp" +#include "../core/fwd.hpp" +#include "../core/iterator.hpp" +#include "../core/type_info.hpp" #include "../core/type_traits.hpp" -#include "../signal/sigh.hpp" +#include "../core/utility.hpp" +#include "component.hpp" +#include "entity.hpp" +#include "fwd.hpp" +#include "group.hpp" #include "runtime_view.hpp" #include "sparse_set.hpp" -#include "snapshot.hpp" #include "storage.hpp" -#include "entity.hpp" -#include "group.hpp" +#include "utility.hpp" #include "view.hpp" -#include "fwd.hpp" - namespace entt { - /** - * @brief Alias for exclusion lists. - * @tparam Type List of types. + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. */ -template -struct exclude_t: type_list {}; +namespace internal { -/** - * @brief Variable template for exclusion lists. - * @tparam Type List of types. - */ -template -constexpr exclude_t exclude{}; +template +class storage_proxy_iterator final { + template + friend class storage_proxy_iterator; + using mapped_type = std::remove_reference_t()->second)>; -/** - * @brief Fast and reliable entity-component system. - * - * The registry is the core class of the entity-component framework.
- * It stores entities and arranges pools of components on a per request basis. - * By means of a registry, users can manage entities and components and thus - * create views or groups to iterate them. - * - * @tparam Entity A valid entity type (see entt_traits for more details). - */ -template -class basic_registry { - using context_family = family; - using component_family = family; - using traits_type = entt_traits; +public: + using value_type = std::pair &>; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; - template - struct pool_handler: storage { - using reference_type = std::conditional_t, const Component &, Component &>; - - sigh on_construct; - sigh on_replace; - sigh on_destroy; - void *group{}; - - pool_handler() ENTT_NOEXCEPT = default; - - pool_handler(const storage &other) - : storage{other} - {} - - template - decltype(auto) assign(basic_registry ®istry, const Entity entt, Args &&... args) { - if constexpr(std::is_empty_v) { - storage::construct(entt); - on_construct.publish(registry, entt, Component{}); - return Component{std::forward(args)...}; - } else { - auto &component = storage::construct(entt, std::forward(args)...); - on_construct.publish(registry, entt, component); - return component; - } - } + storage_proxy_iterator() ENTT_NOEXCEPT + : it{} {} - template - Component * batch(basic_registry ®istry, It first, It last) { - Component *component = nullptr; + storage_proxy_iterator(const It iter) ENTT_NOEXCEPT + : it{iter} {} - if constexpr(std::is_empty_v) { - storage::batch(first, last); + template && std::is_constructible_v>> + storage_proxy_iterator(const storage_proxy_iterator &other) ENTT_NOEXCEPT + : it{other.it} {} - if(!on_construct.empty()) { - std::for_each(first, last, [®istry, this](const auto entt) { - on_construct.publish(registry, entt, Component{}); - }); - } - } else { - component = storage::batch(first, last); + storage_proxy_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } - if(!on_construct.empty()) { - std::for_each(first, last, [®istry, component, this](const auto entt) mutable { - on_construct.publish(registry, entt, *(component++)); - }); - } - } + storage_proxy_iterator operator++(int) ENTT_NOEXCEPT { + storage_proxy_iterator orig = *this; + return ++(*this), orig; + } - return component; - } + storage_proxy_iterator &operator--() ENTT_NOEXCEPT { + return --it, *this; + } - void remove(basic_registry ®istry, const Entity entt) { - on_destroy.publish(registry, entt); - storage::destroy(entt); - } + storage_proxy_iterator operator--(int) ENTT_NOEXCEPT { + storage_proxy_iterator orig = *this; + return operator--(), orig; + } - template - decltype(auto) replace(basic_registry ®istry, const Entity entt, Args &&... args) { - if constexpr(std::is_empty_v) { - ENTT_ASSERT((storage::has(entt))); - on_replace.publish(registry, entt, Component{}); - return Component{std::forward(args)...}; - } else { - Component component{std::forward(args)...}; - on_replace.publish(registry, entt, component); - return (storage::get(entt) = std::move(component)); - } - } - }; + storage_proxy_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + it += value; + return *this; + } - template - using pool_type = pool_handler>; + storage_proxy_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + storage_proxy_iterator copy = *this; + return (copy += value); + } - template - struct group_handler; + storage_proxy_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); + } - template - struct group_handler, type_list>: sparse_set { - template - void maybe_valid_if(basic_registry ®, const Entity entt, const Args &...) { - if constexpr(std::disjunction_v...>) { - if(((std::is_same_v || reg.pool()->has(entt)) && ...) && !(reg.pool()->has(entt) || ...)) { - this->construct(entt); - } - } else if constexpr(std::disjunction_v...>) { - if((reg.pool()->has(entt) && ...) && ((std::is_same_v || !reg.pool()->has(entt)) && ...)) { - this->construct(entt); - } - } - } + storage_proxy_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); + } - template - void discard_if(basic_registry &, const Entity entt, const Args &...) { - if(this->has(entt)) { - this->destroy(entt); - } - } - }; + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + return {it[value].first, *it[value].second}; + } - template - struct group_handler, type_list, Owned...>: sparse_set { - std::size_t owned{}; - - template - void maybe_valid_if(basic_registry ®, const Entity entt, const Args &...) { - const auto cpools = std::make_tuple(reg.pool()...); - - if constexpr(std::disjunction_v..., std::is_same...>) { - if(((std::is_same_v || std::get *>(cpools)->has(entt)) && ...) - && ((std::is_same_v || reg.pool()->has(entt)) && ...) - && !(reg.pool()->has(entt) || ...)) - { - const auto pos = this->owned++; - (std::get *>(cpools)->swap(std::get *>(cpools)->sparse_set::get(entt), pos), ...); - } - } else if constexpr(std::disjunction_v...>) { - if((std::get *>(cpools)->has(entt) && ...) - && (reg.pool()->has(entt) && ...) - && ((std::is_same_v || !reg.pool()->has(entt)) && ...)) - { - const auto pos = this->owned++; - (std::get *>(cpools)->swap(std::get *>(cpools)->sparse_set::get(entt), pos), ...); - } - } - } + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return {it->first, *it->second}; + } - template - void discard_if(basic_registry ®, const Entity entt, const Args &...) { - const auto cpools = std::make_tuple(reg.pool()...); + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } - if(std::get<0>(cpools)->has(entt) && std::get<0>(cpools)->sparse_set::get(entt) < this->owned) { - const auto pos = --this->owned; - (std::get *>(cpools)->swap(std::get *>(cpools)->sparse_set::get(entt), pos), ...); - } - } - }; + template + friend std::ptrdiff_t operator-(const storage_proxy_iterator &, const storage_proxy_iterator &) ENTT_NOEXCEPT; - struct pool_data { - std::unique_ptr> pool; - std::unique_ptr> (* clone)(const sparse_set &); - void (* remove)(basic_registry &, const Entity); - ENTT_ID_TYPE runtime_type; - }; + template + friend bool operator==(const storage_proxy_iterator &, const storage_proxy_iterator &) ENTT_NOEXCEPT; - struct group_data { - const std::size_t extent[3]; - std::unique_ptr group; - bool(* const is_same)(const ENTT_ID_TYPE *); - }; + template + friend bool operator<(const storage_proxy_iterator &, const storage_proxy_iterator &) ENTT_NOEXCEPT; - struct ctx_variable { - std::unique_ptr value; - ENTT_ID_TYPE runtime_type; - }; +private: + It it; +}; - template - static ENTT_ID_TYPE runtime_type() ENTT_NOEXCEPT { - if constexpr(is_named_type_v) { - return named_type_traits::value; - } else { - return Family::template type; - } +template +[[nodiscard]] std::ptrdiff_t operator-(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it - rhs.it; +} + +template +[[nodiscard]] bool operator==(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] bool operator!=(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +[[nodiscard]] bool operator<(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it < rhs.it; +} + +template +[[nodiscard]] bool operator>(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +template +[[nodiscard]] bool operator<=(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +template +[[nodiscard]] bool operator>=(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +struct registry_context { + template + Type &emplace_hint(const id_type id, Args &&...args) { + return any_cast(data.try_emplace(id, std::in_place_type, std::forward(args)...).first->second); } - void release(const Entity entity) { - // lengthens the implicit list of destroyed entities - const auto entt = entity & traits_type::entity_mask; - const auto version = ((entity >> traits_type::entity_shift) + 1) << traits_type::entity_shift; - const auto node = (available ? next : ((entt + 1) & traits_type::entity_mask)) | version; - entities[entt] = node; - next = entt; - ++available; + template + Type &emplace(Args &&...args) { + return emplace_hint(type_id().hash(), std::forward(args)...); } - template - inline const auto * pool() const ENTT_NOEXCEPT { - const auto ctype = type(); + template + bool erase(const id_type id = type_id().hash()) { + const auto it = data.find(id); + return it != data.end() && it->second.type() == type_id() ? (data.erase(it), true) : false; + } - if constexpr(is_named_type_v) { - const auto it = std::find_if(pools.begin()+skip_family_pools, pools.end(), [ctype](const auto &candidate) { - return candidate.runtime_type == ctype; - }); + template + [[nodiscard]] std::add_const_t &at(const id_type id = type_id().hash()) const { + return any_cast &>(data.at(id)); + } - return it == pools.cend() ? nullptr : static_cast *>(it->pool.get()); - } else { - return ctype < skip_family_pools ? static_cast *>(pools[ctype].pool.get()) : nullptr; - } + template + [[nodiscard]] Type &at(const id_type id = type_id().hash()) { + return any_cast(data.at(id)); } - template - inline auto * pool() ENTT_NOEXCEPT { - return const_cast *>(std::as_const(*this).template pool()); + template + [[nodiscard]] std::add_const_t *find(const id_type id = type_id().hash()) const { + const auto it = data.find(id); + return it != data.cend() ? any_cast>(&it->second) : nullptr; + } + + template + [[nodiscard]] Type *find(const id_type id = type_id().hash()) { + const auto it = data.find(id); + return it != data.end() ? any_cast(&it->second) : nullptr; } + template + [[nodiscard]] bool contains(const id_type id = type_id().hash()) const { + const auto it = data.find(id); + return it != data.end() && it->second.type() == type_id(); + } + +private: + dense_map, identity> data; +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Fast and reliable entity-component system. + * @tparam Entity A valid entity type (see entt_traits for more details). + */ +template +class basic_registry { + using entity_traits = entt_traits; + using basic_common_type = basic_sparse_set; + template - auto * assure() { - const auto ctype = type(); - pool_data *pdata = nullptr; + using storage_type = typename storage_traits::storage_type; - if constexpr(is_named_type_v) { - const auto it = std::find_if(pools.begin()+skip_family_pools, pools.end(), [ctype](const auto &candidate) { - return candidate.runtime_type == ctype; - }); + template + struct group_handler; - pdata = (it == pools.cend() ? &pools.emplace_back() : &(*it)); - } else { - if(!(ctype < skip_family_pools)) { - pools.reserve(pools.size()+ctype-skip_family_pools+1); + template + struct group_handler, get_t, Owned...> { + // nasty workaround for an issue with the toolset v141 that doesn't accept a fold expression here + static_assert(!std::disjunction_v::in_place_delete>...>, "Groups do not support in-place delete"); + std::conditional_t current{}; + + template + void maybe_valid_if(basic_registry &owner, const Entity entt) { + [[maybe_unused]] const auto cpools = std::forward_as_tuple(owner.assure()...); + + const auto is_valid = ((std::is_same_v || std::get &>(cpools).contains(entt)) && ...) + && ((std::is_same_v || owner.assure().contains(entt)) && ...) + && ((std::is_same_v || !owner.assure().contains(entt)) && ...); + + if constexpr(sizeof...(Owned) == 0) { + if(is_valid && !current.contains(entt)) { + current.emplace(entt); + } + } else { + if(is_valid && !(std::get<0>(cpools).index(entt) < current)) { + const auto pos = current++; + (std::get &>(cpools).swap_elements(std::get &>(cpools).data()[pos], entt), ...); + } + } + } - while(!(ctype < skip_family_pools)) { - pools.emplace(pools.begin()+(skip_family_pools++), pool_data{}); + void discard_if([[maybe_unused]] basic_registry &owner, const Entity entt) { + if constexpr(sizeof...(Owned) == 0) { + current.remove(entt); + } else { + if(const auto cpools = std::forward_as_tuple(owner.assure()...); std::get<0>(cpools).contains(entt) && (std::get<0>(cpools).index(entt) < current)) { + const auto pos = --current; + (std::get &>(cpools).swap_elements(std::get &>(cpools).data()[pos], entt), ...); } } + } + }; + + struct group_data { + std::size_t size; + std::unique_ptr group; + bool (*owned)(const id_type) ENTT_NOEXCEPT; + bool (*get)(const id_type) ENTT_NOEXCEPT; + bool (*exclude)(const id_type) ENTT_NOEXCEPT; + }; - pdata = &pools[ctype]; + template + [[nodiscard]] auto &assure(const id_type id = type_hash::value()) { + static_assert(std::is_same_v>, "Non-decayed types not allowed"); + auto &&cpool = pools[id]; + + if(!cpool) { + cpool.reset(new storage_type{}); + cpool->bind(forward_as_any(*this)); } - if(!pdata->pool) { - pdata->runtime_type = ctype; - pdata->pool = std::make_unique>(); + ENTT_ASSERT(cpool->type() == type_id(), "Unexpected type"); + return static_cast &>(*cpool); + } - pdata->clone = +[](const sparse_set &other) -> std::unique_ptr> { - if constexpr(std::is_copy_constructible_v>) { - return std::make_unique>(static_cast &>(other)); - } else { - return nullptr; - } - }; + template + [[nodiscard]] const auto &assure(const id_type id = type_hash::value()) const { + static_assert(std::is_same_v>, "Non-decayed types not allowed"); - pdata->remove = +[](basic_registry ®istry, const Entity entt) { - registry.pool()->remove(registry, entt); - }; + if(const auto it = pools.find(id); it != pools.cend()) { + ENTT_ASSERT(it->second->type() == type_id(), "Unexpected type"); + return static_cast &>(*it->second); } - return static_cast *>(pdata->pool.get()); + static storage_type placeholder{}; + return placeholder; + } + + auto generate_identifier(const std::size_t pos) ENTT_NOEXCEPT { + ENTT_ASSERT(pos < entity_traits::to_entity(null), "No entities available"); + return entity_traits::combine(static_cast(pos), {}); + } + + auto recycle_identifier() ENTT_NOEXCEPT { + ENTT_ASSERT(free_list != null, "No entities available"); + const auto curr = entity_traits::to_entity(free_list); + free_list = entity_traits::combine(entity_traits::to_integral(entities[curr]), tombstone); + return (entities[curr] = entity_traits::combine(curr, entity_traits::to_integral(entities[curr]))); + } + + auto release_entity(const Entity entity, const typename entity_traits::version_type version) { + const typename entity_traits::version_type vers = version + (version == entity_traits::to_version(tombstone)); + entities[entity_traits::to_entity(entity)] = entity_traits::construct(entity_traits::to_integral(free_list), vers); + free_list = entity_traits::combine(entity_traits::to_integral(entity), tombstone); + return vers; } public: /*! @brief Underlying entity identifier. */ - using entity_type = typename traits_type::entity_type; + using entity_type = Entity; /*! @brief Underlying version type. */ - using version_type = typename traits_type::version_type; - /*! @brief Unsigned integer type. */ - using size_type = typename sparse_set::size_type; + using version_type = typename entity_traits::version_type; /*! @brief Unsigned integer type. */ - using component_type = ENTT_ID_TYPE; + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using base_type = basic_common_type; + /*! @brief Context type. */ + using context = internal::registry_context; /*! @brief Default constructor. */ - basic_registry() ENTT_NOEXCEPT = default; + basic_registry() + : pools{}, + groups{}, + entities{}, + free_list{tombstone}, + vars{} {} + + /** + * @brief Allocates enough memory upon construction to store `count` pools. + * @param count The number of pools to allocate memory for. + */ + basic_registry(const size_type count) + : pools{}, + groups{}, + entities{}, + free_list{tombstone}, + vars{} { + pools.reserve(count); + } + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_registry(basic_registry &&other) + : pools{std::move(other.pools)}, + groups{std::move(other.groups)}, + entities{std::move(other.entities)}, + free_list{other.free_list}, + vars{std::move(other.vars)} { + for(auto &&curr: pools) { + curr.second->bind(forward_as_any(*this)); + } + } - /*! @brief Default move constructor. */ - basic_registry(basic_registry &&) = default; + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This registry. + */ + basic_registry &operator=(basic_registry &&other) { + pools = std::move(other.pools); + groups = std::move(other.groups); + entities = std::move(other.entities); + free_list = other.free_list; + vars = std::move(other.vars); + + for(auto &&curr: pools) { + curr.second->bind(forward_as_any(*this)); + } - /*! @brief Default move assignment operator. @return This registry. */ - basic_registry & operator=(basic_registry &&) = default; + return *this; + } /** - * @brief Returns the numeric identifier of a component. + * @brief Returns an iterable object to use to _visit_ a registry. * - * The given component doesn't need to be necessarily in use.
- * Do not use this functionality to generate numeric identifiers for types - * at runtime. They aren't guaranteed to be stable between different runs. + * The iterable object returns a pair that contains the name and a reference + * to the current storage. * - * @tparam Component Type of component to query. - * @return Runtime numeric identifier of the given type of component. + * @return An iterable object to use to _visit_ the registry. */ - template - inline static component_type type() ENTT_NOEXCEPT { - return runtime_type(); + [[nodiscard]] auto storage() ENTT_NOEXCEPT { + return iterable_adaptor{internal::storage_proxy_iterator{pools.begin()}, internal::storage_proxy_iterator{pools.end()}}; } - /** - * @brief Returns the number of existing components of the given type. - * @tparam Component Type of component of which to return the size. - * @return Number of existing components of the given type. - */ - template - size_type size() const ENTT_NOEXCEPT { - const auto *cpool = pool(); - return cpool ? cpool->size() : size_type{}; + /*! @copydoc storage */ + [[nodiscard]] auto storage() const ENTT_NOEXCEPT { + return iterable_adaptor{internal::storage_proxy_iterator{pools.cbegin()}, internal::storage_proxy_iterator{pools.cend()}}; } /** - * @brief Returns the number of entities created so far. - * @return Number of entities created so far. + * @brief Finds the storage associated with a given name, if any. + * @param id Name used to map the storage within the registry. + * @return An iterator to the given storage if it's found, past the end + * iterator otherwise. */ - size_type size() const ENTT_NOEXCEPT { - return entities.size(); + [[nodiscard]] auto storage(const id_type id) { + return internal::storage_proxy_iterator{pools.find(id)}; } /** - * @brief Returns the number of entities still in use. - * @return Number of entities still in use. + * @brief Finds the storage associated with a given name, if any. + * @param id Name used to map the storage within the registry. + * @return An iterator to the given storage if it's found, past the end + * iterator otherwise. */ - size_type alive() const ENTT_NOEXCEPT { - return entities.size() - available; + [[nodiscard]] auto storage(const id_type id) const { + return internal::storage_proxy_iterator{pools.find(id)}; } /** - * @brief Increases the capacity of the pool for the given component. - * - * If the new capacity is greater than the current capacity, new storage is - * allocated, otherwise the method does nothing. - * - * @tparam Component Type of component for which to reserve storage. - * @param cap Desired capacity. + * @brief Returns the storage for a given component type. + * @tparam Component Type of component of which to return the storage. + * @param id Optional name used to map the storage within the registry. + * @return The storage for the given component type. */ template - void reserve(const size_type cap) { - assure()->reserve(cap); + decltype(auto) storage(const id_type id = type_hash>::value()) { + if constexpr(std::is_const_v) { + return std::as_const(*this).template storage>(id); + } else { + return assure(id); + } } /** - * @brief Increases the capacity of a registry in terms of entities. + * @brief Returns the storage for a given component type. * - * If the new capacity is greater than the current capacity, new storage is - * allocated, otherwise the method does nothing. + * @warning + * If a storage for the given component doesn't exist yet, a temporary + * placeholder is returned instead. * - * @param cap Desired capacity. + * @tparam Component Type of component of which to return the storage. + * @param id Optional name used to map the storage within the registry. + * @return The storage for the given component type. */ - void reserve(const size_type cap) { - entities.reserve(cap); + template + decltype(auto) storage(const id_type id = type_hash>::value()) const { + return assure>(id); } /** - * @brief Returns the capacity of the pool for the given component. - * @tparam Component Type of component in which one is interested. - * @return Capacity of the pool of the given component. + * @brief Returns the number of entities created so far. + * @return Number of entities created so far. */ - template - size_type capacity() const ENTT_NOEXCEPT { - const auto *cpool = pool(); - return cpool ? cpool->capacity() : size_type{}; + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return entities.size(); } /** - * @brief Returns the number of entities that a registry has currently - * allocated space for. - * @return Capacity of the registry. + * @brief Returns the number of entities still in use. + * @return Number of entities still in use. */ - size_type capacity() const ENTT_NOEXCEPT { - return entities.capacity(); + [[nodiscard]] size_type alive() const { + auto sz = entities.size(); + + for(auto curr = free_list; curr != null; --sz) { + curr = entities[entity_traits::to_entity(curr)]; + } + + return sz; } /** - * @brief Requests the removal of unused capacity for a given component. - * @tparam Component Type of component for which to reclaim unused capacity. + * @brief Increases the capacity (number of entities) of the registry. + * @param cap Desired capacity. */ - template - void shrink_to_fit() { - assure()->shrink_to_fit(); + void reserve(const size_type cap) { + entities.reserve(cap); } /** - * @brief Checks whether the pool of a given component is empty. - * @tparam Component Type of component in which one is interested. - * @return True if the pool of the given component is empty, false - * otherwise. + * @brief Returns the number of entities that a registry has currently + * allocated space for. + * @return Capacity of the registry. */ - template - bool empty() const ENTT_NOEXCEPT { - const auto *cpool = pool(); - return cpool ? cpool->empty() : true; + [[nodiscard]] size_type capacity() const ENTT_NOEXCEPT { + return entities.capacity(); } /** - * @brief Checks if there exists at least an entity still in use. - * @return True if at least an entity is still in use, false otherwise. + * @brief Checks whether the registry is empty (no entities still in use). + * @return True if the registry is empty, false otherwise. */ - bool empty() const ENTT_NOEXCEPT { - return entities.size() == available; + [[nodiscard]] bool empty() const { + return !alive(); } /** - * @brief Direct access to the list of components of a given pool. + * @brief Direct access to the list of entities of a registry. * - * The returned pointer is such that range - * `[raw(), raw() + size()]` is always a - * valid range, even if the container is empty. + * The returned pointer is such that range `[data(), data() + size())` is + * always a valid range, even if the registry is empty. * - * There are no guarantees on the order of the components. Use a view if you - * want to iterate entities and components in the expected order. - * - * @note - * Empty components aren't explicitly instantiated. Only one instance of the - * given type is created. Therefore, this function always returns a pointer - * to that instance. + * @warning + * This list contains both valid and destroyed entities and isn't suitable + * for direct use. * - * @tparam Component Type of component in which one is interested. - * @return A pointer to the array of components of the given type. + * @return A pointer to the array of entities. */ - template - const Component * raw() const ENTT_NOEXCEPT { - const auto *cpool = pool(); - return cpool ? cpool->raw() : nullptr; - } - - /*! @copydoc raw */ - template - inline Component * raw() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).template raw()); + [[nodiscard]] const entity_type *data() const ENTT_NOEXCEPT { + return entities.data(); } /** - * @brief Direct access to the list of entities of a given pool. + * @brief Returns the head of the list of released entities. * - * The returned pointer is such that range - * `[data(), data() + size()]` is always a - * valid range, even if the container is empty. + * This function is intended for use in conjunction with `assign`.
+ * The returned entity has an invalid identifier in all cases. * - * There are no guarantees on the order of the entities. Use a view if you - * want to iterate entities and components in the expected order. - * - * @tparam Component Type of component in which one is interested. - * @return A pointer to the array of entities. + * @return The head of the list of released entities. */ - template - const entity_type * data() const ENTT_NOEXCEPT { - const auto *cpool = pool(); - return cpool ? cpool->data() : nullptr; + [[nodiscard]] entity_type released() const ENTT_NOEXCEPT { + return free_list; } /** - * @brief Checks if an entity identifier refers to a valid entity. - * @param entity An entity identifier, either valid or not. + * @brief Checks if an identifier refers to a valid entity. + * @param entity An identifier, either valid or not. * @return True if the identifier is valid, false otherwise. */ - bool valid(const entity_type entity) const ENTT_NOEXCEPT { - const auto pos = size_type(entity & traits_type::entity_mask); + [[nodiscard]] bool valid(const entity_type entity) const { + const auto pos = size_type(entity_traits::to_entity(entity)); return (pos < entities.size() && entities[pos] == entity); } /** - * @brief Returns the entity identifier without the version. - * @param entity An entity identifier, either valid or not. - * @return The entity identifier without the version. - */ - static entity_type entity(const entity_type entity) ENTT_NOEXCEPT { - return entity & traits_type::entity_mask; - } - - /** - * @brief Returns the version stored along with an entity identifier. - * @param entity An entity identifier, either valid or not. - * @return The version stored along with the given entity identifier. + * @brief Returns the actual version for an identifier. + * @param entity A valid identifier. + * @return The version for the given identifier if valid, the tombstone + * version otherwise. */ - static version_type version(const entity_type entity) ENTT_NOEXCEPT { - return version_type(entity >> traits_type::entity_shift); + [[nodiscard]] version_type current(const entity_type entity) const { + const auto pos = size_type(entity_traits::to_entity(entity)); + return entity_traits::to_version(pos < entities.size() ? entities[pos] : tombstone); } /** - * @brief Returns the actual version for an entity identifier. - * - * @warning - * Attempting to use an entity that doesn't belong to the registry results - * in undefined behavior. An entity belongs to the registry even if it has - * been previously destroyed and/or recycled.
- * An assertion will abort the execution at runtime in debug mode if the - * registry doesn't own the given entity. - * - * @param entity A valid entity identifier. - * @return Actual version for the given entity identifier. + * @brief Creates a new entity or recycles a destroyed one. + * @return A valid identifier. */ - version_type current(const entity_type entity) const ENTT_NOEXCEPT { - const auto pos = size_type(entity & traits_type::entity_mask); - ENTT_ASSERT(pos < entities.size()); - return version_type(entities[pos] >> traits_type::entity_shift); + [[nodiscard]] entity_type create() { + return (free_list == null) ? entities.emplace_back(generate_identifier(entities.size())) : recycle_identifier(); } /** - * @brief Creates a new entity and returns it. - * - * There are two kinds of entity identifiers: + * @copybrief create * - * * Newly created ones in case no entities have been previously destroyed. - * * Recycled ones with updated versions. + * If the requested entity isn't in use, the suggested identifier is used. + * Otherwise, a new identifier is generated. * - * Users should not care about the type of the returned entity identifier. - * In case entity identifers are stored around, the `valid` member - * function can be used to know if they are still valid or the entity has - * been destroyed and potentially recycled. - * - * The returned entity has assigned the given components, if any. The - * components must be at least default constructible. A compilation error - * will occur otherwhise. - * - * @tparam Component Types of components to assign to the entity. - * @return A valid entity identifier if the component list is empty, a tuple - * containing the entity identifier and the references to the components - * just created otherwise. + * @param hint Required identifier. + * @return A valid identifier. */ - template - decltype(auto) create() { - entity_type entity; - - if(available) { - const auto entt = next; - const auto version = entities[entt] & (traits_type::version_mask << traits_type::entity_shift); - next = entities[entt] & traits_type::entity_mask; - entity = entt | version; - entities[entt] = entity; - --available; - } else { - entity = entities.emplace_back(entity_type(entities.size())); - // traits_type::entity_mask is reserved to allow for null identifiers - ENTT_ASSERT(entity < traits_type::entity_mask); - } + [[nodiscard]] entity_type create(const entity_type hint) { + const auto length = entities.size(); - if constexpr(sizeof...(Component) == 0) { - return entity; + if(hint == null || hint == tombstone) { + return create(); + } else if(const auto req = entity_traits::to_entity(hint); !(req < length)) { + entities.resize(size_type(req) + 1u, null); + + for(auto pos = length; pos < req; ++pos) { + release_entity(generate_identifier(pos), {}); + } + + return (entities[req] = hint); + } else if(const auto curr = entity_traits::to_entity(entities[req]); req == curr) { + return create(); } else { - return std::tuple(entity))...>{entity, assign(entity)...}; + auto *it = &free_list; + for(; entity_traits::to_entity(*it) != req; it = &entities[entity_traits::to_entity(*it)]) {} + *it = entity_traits::combine(curr, entity_traits::to_integral(*it)); + return (entities[req] = hint); } } /** - * @brief Assigns each element in a range an entity. + * @brief Assigns each element in a range an identifier. * * @sa create * - * @tparam Component Types of components to assign to the entity. * @tparam It Type of forward 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. - * @return No return value if the component list is empty, a tuple - * containing the pointers to the arrays of components just created and - * sorted the same of the entities otherwise. */ - template - auto create(It first, It last) { - static_assert(std::is_convertible_v::value_type>); - const auto length = size_type(std::distance(first, last)); - const auto sz = std::min(available, length); - [[maybe_unused]] entity_type candidate{}; - - available -= sz; - - const auto tail = std::generate_n(first, sz, [&candidate, this]() mutable { - if constexpr(sizeof...(Component) > 0) { - candidate = std::max(candidate, next); - } else { - // suppress warnings - (void)candidate; - } - - const auto entt = next; - const auto version = entities[entt] & (traits_type::version_mask << traits_type::entity_shift); - next = entities[entt] & traits_type::entity_mask; - return (entities[entt] = entt | version); - }); + template + void create(It first, It last) { + for(; free_list != null && first != last; ++first) { + *first = recycle_identifier(); + } - std::generate(tail, last, [this]() { - return entities.emplace_back(entity_type(entities.size())); - }); + const auto length = entities.size(); + entities.resize(length + std::distance(first, last), null); - if constexpr(sizeof...(Component) > 0) { - return std::make_tuple(assure()->batch(*this, first, last)...); + for(auto pos = length; first != last; ++first, ++pos) { + *first = entities[pos] = generate_identifier(pos); } } /** - * @brief Destroys an entity and lets the registry recycle the identifier. + * @brief Assigns identifiers to an empty registry. * - * When an entity is destroyed, its version is updated and the identifier - * can be recycled at any time. In case entity identifers are stored around, - * the `valid` member function can be used to know if they are still valid - * or the entity has been destroyed and potentially recycled. + * This function is intended for use in conjunction with `data`, `size` and + * `destroyed`.
+ * Don't try to inject ranges of randomly generated entities nor the _wrong_ + * head for the list of destroyed entities. There is no guarantee that a + * registry will continue to work properly in this case. * * @warning - * In case there are listeners that observe the destruction of components - * and assign other components to the entity in their bodies, the result of - * invoking this function may not be as expected. In the worst case, it - * could lead to undefined behavior. An assertion will abort the execution - * at runtime in debug mode if a violation is detected. + * There must be no entities still alive for this to work properly. + * + * @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 destroyed The head of the list of destroyed entities. + */ + template + void assign(It first, It last, const entity_type destroyed) { + ENTT_ASSERT(!alive(), "Entities still alive"); + entities.assign(first, last); + free_list = destroyed; + } + + /** + * @brief Releases an identifier. + * + * The version is updated and the identifier can be recycled at any time. * * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. + * Attempting to use an invalid entity results in undefined behavior. * - * @param entity A valid entity identifier. + * @param entity A valid identifier. + * @return The version of the recycled entity. */ - void destroy(const entity_type entity) { - ENTT_ASSERT(valid(entity)); - - for(auto pos = pools.size(); pos; --pos) { - if(auto &pdata = pools[pos-1]; pdata.pool && pdata.pool->has(entity)) { - pdata.remove(*this, entity); - } - }; - - // just a way to protect users from listeners that attach components - ENTT_ASSERT(orphan(entity)); - release(entity); + version_type release(const entity_type entity) { + return release(entity, static_cast(entity_traits::to_version(entity) + 1u)); } /** - * @brief Destroys all the entities in a range. - * @tparam It Type of forward 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 - void destroy(It first, It last) { - ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); })); - - for(auto pos = pools.size(); pos; --pos) { - if(auto &pdata = pools[pos-1]; pdata.pool) { - std::for_each(first, last, [&pdata, this](const auto entity) { - if(pdata.pool->has(entity)) { - pdata.remove(*this, entity); - } - }); - } - }; - - // just a way to protect users from listeners that attach components - ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return orphan(entity); })); - - std::for_each(first, last, [this](const auto entity) { - release(entity); - }); - } - - /** - * @brief Assigns the given component to an entity. + * @brief Releases an identifier. * - * A new instance of the given component is created and initialized with the - * arguments provided (the component must have a proper constructor or be of - * aggregate type). Then the component is assigned to the given entity. + * The suggested version or the valid version closest to the suggested one + * is used instead of the implicitly generated version. * - * @warning - * Attempting to use an invalid entity or to assign a component to an entity - * that already owns it results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity or if the entity already owns an instance of the given - * component. + * @sa release * - * @tparam Component Type of component to create. - * @tparam Args Types of arguments to use to construct the component. - * @param entity A valid entity identifier. - * @param args Parameters to use to initialize the component. - * @return A reference to the newly created component. + * @param entity A valid identifier. + * @param version A desired version upon destruction. + * @return The version actually assigned to the entity. */ - template - decltype(auto) assign(const entity_type entity, [[maybe_unused]] Args &&... args) { - ENTT_ASSERT(valid(entity)); - return assure()->assign(*this, entity, std::forward(args)...); + version_type release(const entity_type entity, const version_type version) { + ENTT_ASSERT(orphan(entity), "Non-orphan entity"); + return release_entity(entity, version); } /** - * @brief Removes the given component from an entity. + * @brief Releases all identifiers in a range. * - * @warning - * Attempting to use an invalid entity or to remove a component from an - * entity that doesn't own it results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity or if the entity doesn't own an instance of the given - * component. + * @sa release * - * @tparam Component Type of component to remove. - * @param entity A valid entity identifier. + * @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 remove(const entity_type entity) { - ENTT_ASSERT(valid(entity)); - pool()->remove(*this, entity); + template + void release(It first, It last) { + for(; first != last; ++first) { + release(*first); + } } /** - * @brief Checks if an entity has all the given components. + * @brief Destroys an entity and releases its identifier. + * + * @sa release * * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. + * Adding or removing components to an entity that is being destroyed can + * result in undefined behavior. Attempting to use an invalid entity results + * in undefined behavior. * - * @tparam Component Components for which to perform the check. - * @param entity A valid entity identifier. - * @return True if the entity has all the components, false otherwise. + * @param entity A valid identifier. + * @return The version of the recycled entity. */ - template - bool has(const entity_type entity) const ENTT_NOEXCEPT { - ENTT_ASSERT(valid(entity)); - [[maybe_unused]] const auto cpools = std::make_tuple(pool()...); - return ((std::get *>(cpools) ? std::get *>(cpools)->has(entity) : false) && ...); + version_type destroy(const entity_type entity) { + return destroy(entity, static_cast(entity_traits::to_version(entity) + 1u)); } /** - * @brief Returns references to the given components for an entity. + * @brief Destroys an entity and releases its identifier. * - * @warning - * Attempting to use an invalid entity or to get a component from an entity - * that doesn't own it results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity or if the entity doesn't own an instance of the given - * component. + * The suggested version or the valid version closest to the suggested one + * is used instead of the implicitly generated version. * - * @tparam Component Types of components to get. - * @param entity A valid entity identifier. - * @return References to the components owned by the entity. + * @sa destroy + * + * @param entity A valid identifier. + * @param version A desired version upon destruction. + * @return The version actually assigned to the entity. */ - template - decltype(auto) get([[maybe_unused]] const entity_type entity) const { - ENTT_ASSERT(valid(entity)); + version_type destroy(const entity_type entity, const version_type version) { + ENTT_ASSERT(valid(entity), "Invalid entity"); - if constexpr(sizeof...(Component) == 1) { - return (pool()->get(entity), ...); - } else { - return std::tuple(entity))...>{get(entity)...}; + for(size_type pos = pools.size(); pos; --pos) { + pools.begin()[pos - 1u].second->remove(entity); } - } - - /*! @copydoc get */ - template - inline decltype(auto) get([[maybe_unused]] const entity_type entity) ENTT_NOEXCEPT { - ENTT_ASSERT(valid(entity)); - if constexpr(sizeof...(Component) == 1) { - return (pool()->get(entity), ...); - } else { - return std::tuple(entity))...>{get(entity)...}; - } + return release_entity(entity, version); } /** - * @brief Returns a reference to the given component for an entity. + * @brief Destroys all entities in a range and releases their identifiers. * - * In case the entity doesn't own the component, the parameters provided are - * used to construct it.
- * Equivalent to the following snippet (pseudocode): + * @sa destroy * - * @code{.cpp} - * auto &component = registry.has(entity) ? registry.get(entity) : registry.assign(entity, args...); - * @endcode + * @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 destroy(It first, It last) { + for(; first != last; ++first) { + destroy(*first); + } + } + + /** + * @brief Assigns the given component to an entity. * - * Prefer this function anyway because it has slightly better performance. + * The component must have a proper constructor or be of aggregate type. * * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. + * Attempting to use an invalid entity or to assign a component to an entity + * that already owns it results in undefined behavior. * - * @tparam Component Type of component to get. + * @tparam Component Type of component to create. * @tparam Args Types of arguments to use to construct the component. - * @param entity A valid entity identifier. + * @param entity A valid identifier. * @param args Parameters to use to initialize the component. - * @return Reference to the component owned by the entity. + * @return A reference to the newly created component. */ template - decltype(auto) get_or_assign(const entity_type entity, Args &&... args) ENTT_NOEXCEPT { - ENTT_ASSERT(valid(entity)); - auto *cpool = assure(); - return cpool->has(entity) ? cpool->get(entity) : cpool->assign(*this, entity, std::forward(args)...); + decltype(auto) emplace(const entity_type entity, Args &&...args) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return assure().emplace(entity, std::forward(args)...); } /** - * @brief Returns pointers to the given components for an entity. + * @brief Assigns each entity in a range the given component. * - * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. + * @sa emplace * - * @tparam Component Types of components to get. - * @param entity A valid entity identifier. - * @return Pointers to the components owned by the entity. + * @tparam Component Type of component to create. + * @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 value An instance of the component to assign. */ - template - auto try_get([[maybe_unused]] const entity_type entity) const ENTT_NOEXCEPT { - ENTT_ASSERT(valid(entity)); - - if constexpr(sizeof...(Component) == 1) { - const auto cpools = std::make_tuple(pool()...); - return ((std::get *>(cpools) ? std::get *>(cpools)->try_get(entity) : nullptr), ...); - } else { - return std::tuple *...>{try_get(entity)...}; - } - } - - /*! @copydoc try_get */ - template - inline auto try_get([[maybe_unused]] const entity_type entity) ENTT_NOEXCEPT { - if constexpr(sizeof...(Component) == 1) { - return (const_cast(std::as_const(*this).template try_get(entity)), ...); - } else { - return std::tuple{try_get(entity)...}; - } + template + void insert(It first, It last, const Component &value = {}) { + ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity"); + assure().insert(first, last, value); } /** - * @brief Replaces the given component for an entity. + * @brief Assigns each entity in a range the given components. * - * A new instance of the given component is created and initialized with the - * arguments provided (the component must have a proper constructor or be of - * aggregate type). Then the component is assigned to the given entity. + * @sa emplace * - * @warning - * Attempting to use an invalid entity or to replace a component of an - * entity that doesn't own it results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity or if the entity doesn't own an instance of the given - * component. - * - * @tparam Component Type of component to replace. - * @tparam Args Types of arguments to use to construct the component. - * @param entity A valid entity identifier. - * @param args Parameters to use to initialize the component. - * @return A reference to the newly created component. + * @tparam Component Type of component 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 components. */ - template - decltype(auto) replace(const entity_type entity, Args &&... args) { - ENTT_ASSERT(valid(entity)); - return pool()->replace(*this, entity, std::forward(args)...); + template::value_type, Component>>> + void insert(EIt first, EIt last, CIt from) { + ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity"); + assure().insert(first, last, from); } /** * @brief Assigns or replaces the given component for an entity. * - * Equivalent to the following snippet (pseudocode): - * - * @code{.cpp} - * auto &component = registry.has(entity) ? registry.replace(entity, args...) : registry.assign(entity, args...); - * @endcode - * - * Prefer this function anyway because it has slightly better performance. - * * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. + * Attempting to use an invalid entity results in undefined behavior. * * @tparam Component Type of component to assign or replace. * @tparam Args Types of arguments to use to construct the component. - * @param entity A valid entity identifier. + * @param entity A valid identifier. * @param args Parameters to use to initialize the component. * @return A reference to the newly created component. */ template - decltype(auto) assign_or_replace(const entity_type entity, Args &&... args) { - ENTT_ASSERT(valid(entity)); - auto *cpool = assure(); - return cpool->has(entity) ? cpool->replace(*this, entity, std::forward(args)...) : cpool->assign(*this, entity, std::forward(args)...); + decltype(auto) emplace_or_replace(const entity_type entity, Args &&...args) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + auto &cpool = assure(); + + return cpool.contains(entity) + ? cpool.patch(entity, [&args...](auto &...curr) { ((curr = Component{std::forward(args)...}), ...); }) + : cpool.emplace(entity, std::forward(args)...); } /** - * @brief Returns a sink object for the given component. + * @brief Patches the given component for an entity. * - * A sink is an opaque object used to connect listeners to components.
- * The sink returned by this function can be used to receive notifications - * whenever a new instance of the given component is created and assigned to - * an entity. - * - * The function type for a listener is equivalent to: + * The signature of the function should be equivalent to the following: * * @code{.cpp} - * void(registry &, Entity, Component &); + * void(Component &); * @endcode * - * Listeners are invoked **after** the component has been assigned to the - * entity. The order of invocation of the listeners isn't guaranteed. - * * @note - * Empty types aren't explicitly instantiated. Therefore, temporary objects - * are returned through signals. They can be caught only by copy or with - * const references. + * Empty types aren't explicitly instantiated and therefore they are never + * returned. However, this function can be used to trigger an update signal + * for them. * - * @sa sink - * - * @tparam Component Type of component of which to get the sink. - * @return A temporary sink object. + * @warning + * Attempting to use an invalid entity or to patch a component of an entity + * that doesn't own it results in undefined behavior. + * + * @tparam Component Type of component to patch. + * @tparam Func Types of the function objects to invoke. + * @param entity A valid identifier. + * @param func Valid function objects. + * @return A reference to the patched component. */ - template - auto on_construct() ENTT_NOEXCEPT { - return assure()->on_construct.sink(); + template + decltype(auto) patch(const entity_type entity, Func &&...func) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return assure().patch(entity, std::forward(func)...); } /** - * @brief Returns a sink object for the given component. - * - * A sink is an opaque object used to connect listeners to components.
- * The sink returned by this function can be used to receive notifications - * whenever an instance of the given component is explicitly replaced. - * - * The function type for a listener is equivalent to: - * - * @code{.cpp} - * void(registry &, Entity, Component &); - * @endcode - * - * Listeners are invoked **before** the component has been replaced. The - * order of invocation of the listeners isn't guaranteed. + * @brief Replaces the given component for an entity. * - * @note - * Empty types aren't explicitly instantiated. Therefore, temporary objects - * are returned through signals. They can be caught only by copy or with - * const references. + * The component must have a proper constructor or be of aggregate type. * - * @sa sink + * @warning + * Attempting to use an invalid entity or to replace a component of an + * entity that doesn't own it results in undefined behavior. * - * @tparam Component Type of component of which to get the sink. - * @return A temporary sink object. + * @tparam Component Type of component to replace. + * @tparam Args Types of arguments to use to construct the component. + * @param entity A valid identifier. + * @param args Parameters to use to initialize the component. + * @return A reference to the component being replaced. */ - template - auto on_replace() ENTT_NOEXCEPT { - return assure()->on_replace.sink(); + template + decltype(auto) replace(const entity_type entity, Args &&...args) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return assure().patch(entity, [&args...](auto &...curr) { ((curr = Component{std::forward(args)...}), ...); }); } /** - * @brief Returns a sink object for the given component. - * - * A sink is an opaque object used to connect listeners to components.
- * The sink returned by this function can be used to receive notifications - * whenever an instance of the given component is removed from an entity and - * thus destroyed. - * - * The function type for a listener is equivalent to: + * @brief Removes the given components from an entity. * - * @code{.cpp} - * void(registry &, Entity); - * @endcode - * - * Listeners are invoked **before** the component has been removed from the - * entity. The order of invocation of the listeners isn't guaranteed. - * - * @note - * Empty types aren't explicitly instantiated. Therefore, temporary objects - * are returned through signals. They can be caught only by copy or with - * const references. - * - * @sa sink + * @warning + * Attempting to use an invalid entity results in undefined behavior. * - * @tparam Component Type of component of which to get the sink. - * @return A temporary sink object. + * @tparam Component Type of component to remove. + * @tparam Other Other types of components to remove. + * @param entity A valid identifier. + * @return The number of components actually removed. */ - template - auto on_destroy() ENTT_NOEXCEPT { - return assure()->on_destroy.sink(); + template + size_type remove(const entity_type entity) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return (assure().remove(entity) + ... + assure().remove(entity)); } /** - * @brief Sorts the pool of entities for the given component. - * - * The order of the elements in a pool is highly affected by assignments - * of components to entities and deletions. Components are arranged to - * maximize the performance during iterations and users should not make any - * assumption on the order.
- * This function can be used to impose an order to the elements in the pool - * of the given component. The order is kept valid until a component of the - * given type is assigned or removed from an entity. - * - * 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(const Entity, const Entity); - * bool(const Component &, const Component &); - * @endcode - * - * Moreover, the comparison function object shall induce a - * _strict weak ordering_ on the values. - * - * The sort function oject must offer a member function template - * `operator()` that accepts three arguments: + * @brief Removes the given components from all the entities in a range. * - * * 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. + * @sa remove * - * The comparison funtion object received by the sort function object hasn't - * necessarily the type of the one passed along with the other parameters to - * this member function. + * @tparam Component Type of component to remove. + * @tparam Other Other types of components 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 components actually removed. + */ + template + size_type remove(It first, It last) { + if constexpr(sizeof...(Other) == 0u) { + ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity"); + return assure().remove(std::move(first), std::move(last)); + } else { + size_type count{}; + + for(auto cpools = std::forward_as_tuple(assure(), assure()...); first != last; ++first) { + ENTT_ASSERT(valid(*first), "Invalid entity"); + count += std::apply([entt = *first](auto &...curr) { return (curr.remove(entt) + ... + 0u); }, cpools); + } + + return count; + } + } + + /** + * @brief Erases the given components from an entity. * * @warning - * Pools of components that are owned by a group cannot be sorted.
- * An assertion will abort the execution at runtime in debug mode in case - * the pool is owned by a group. + * Attempting to use an invalid entity or to erase a component from an + * entity that doesn't own it results in undefined behavior. * - * @tparam Component Type of components 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. + * @tparam Component Types of components to erase. + * @tparam Other Other types of components to erase. + * @param entity A valid identifier. */ - template - void sort(Compare compare, Sort algo = Sort{}, Args &&... args) { - ENTT_ASSERT(!owned()); - assure()->sort(std::move(compare), std::move(algo), std::forward(args)...); + template + void erase(const entity_type entity) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + (assure().erase(entity), (assure().erase(entity), ...)); } /** - * @brief Sorts two pools of components in the same way. + * @brief Erases the given components from all the entities in a range. * - * The order of the elements in a pool is highly affected by assignments - * of components to entities and deletions. Components are arranged to - * maximize the performance during iterations and users should not make any - * assumption on the order. + * @sa erase * - * It happens that different pools of components must be sorted the same way - * because of runtime and/or performance constraints. This function can be - * used to order a pool of components according to the order between the - * entities in another pool of components. + * @tparam Component Types of components to erase. + * @tparam Other Other types of components 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(sizeof...(Other) == 0u) { + ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity"); + assure().erase(std::move(first), std::move(last)); + } else { + for(auto cpools = std::forward_as_tuple(assure(), assure()...); first != last; ++first) { + ENTT_ASSERT(valid(*first), "Invalid entity"); + std::apply([entt = *first](auto &...curr) { (curr.erase(entt), ...); }, cpools); + } + } + } + + /** + * @brief Removes all tombstones from a registry or only the pools for the + * given components. + * @tparam Component Types of components for which to clear all tombstones. + */ + template + void compact() { + if constexpr(sizeof...(Component) == 0) { + for(auto &&curr: pools) { + curr.second->compact(); + } + } else { + (assure().compact(), ...); + } + } + + /** + * @brief Checks if an entity has all the given components. * - * @b How @b it @b works + * @warning + * Attempting to use an invalid entity results in undefined behavior. * - * Being `A` and `B` the two sets where `B` is the master (the one the order - * of which rules) and `A` is the slave (the one to sort), after a call to - * this function an iterator for `A` will return the entities according to - * the following rules: + * @tparam Component Components for which to perform the check. + * @param entity A valid identifier. + * @return True if the entity has all the components, false otherwise. + */ + template + [[nodiscard]] bool all_of(const entity_type entity) const { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return (assure>().contains(entity) && ...); + } + + /** + * @brief Checks if an entity has at least one of the given components. * - * * All the entities in `A` that are also in `B` are returned first - * according to the order they have in `B`. - * * All the entities in `A` that are not in `B` are returned in no - * particular order after all the other entities. + * @warning + * Attempting to use an invalid entity results in undefined behavior. * - * Any subsequent change to `B` won't affect the order in `A`. + * @tparam Component Components for which to perform the check. + * @param entity A valid identifier. + * @return True if the entity has at least one of the given components, + * false otherwise. + */ + template + [[nodiscard]] bool any_of(const entity_type entity) const { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return (assure>().contains(entity) || ...); + } + + /** + * @brief Returns references to the given components for an entity. * * @warning - * Pools of components that are owned by a group cannot be sorted.
- * An assertion will abort the execution at runtime in debug mode in case - * the pool is owned by a group. + * Attempting to use an invalid entity or to get a component from an entity + * that doesn't own it results in undefined behavior. * - * @tparam To Type of components to sort. - * @tparam From Type of components to use to sort. + * @tparam Component Types of components to get. + * @param entity A valid identifier. + * @return References to the components owned by the entity. */ - template - void sort() { - ENTT_ASSERT(!owned()); - assure()->respect(*assure()); + template + [[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entity) const { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return view().template get(entity); + } + + /*! @copydoc get */ + template + [[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entity) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return view().template get(entity); } /** - * @brief Resets the given component for an entity. + * @brief Returns a reference to the given component for an entity. * - * If the entity has an instance of the component, this function removes the - * component from the entity. Otherwise it does nothing. + * In case the entity doesn't own the component, the parameters provided are + * used to construct it. * * @warning - * Attempting to use an invalid entity results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity. + * Attempting to use an invalid entity results in undefined behavior. * - * @tparam Component Type of component to reset. - * @param entity A valid entity identifier. + * @tparam Component Type of component to get. + * @tparam Args Types of arguments to use to construct the component. + * @param entity A valid identifier. + * @param args Parameters to use to initialize the component. + * @return Reference to the component owned by the entity. */ - template - void reset(const entity_type entity) { - ENTT_ASSERT(valid(entity)); - - if(auto *cpool = assure(); cpool->has(entity)) { - cpool->remove(*this, entity); - } + template + [[nodiscard]] decltype(auto) get_or_emplace(const entity_type entity, Args &&...args) { + ENTT_ASSERT(valid(entity), "Invalid entity"); + auto &cpool = assure(); + return cpool.contains(entity) ? cpool.get(entity) : cpool.emplace(entity, std::forward(args)...); } /** - * @brief Resets the pool of the given component. + * @brief Returns pointers to the given components for an entity. * - * For each entity that has an instance of the given component, the - * component itself is removed and thus destroyed. + * @warning + * Attempting to use an invalid entity results in undefined behavior. * - * @tparam Component Type of component whose pool must be reset. + * @note + * The registry retains ownership of the pointed-to components. + * + * @tparam Component Types of components to get. + * @param entity A valid identifier. + * @return Pointers to the components owned by the entity. */ - template - void reset() { - if(auto *cpool = assure(); cpool->on_destroy.empty()) { - // no group set, otherwise the signal wouldn't be empty - cpool->reset(); + template + [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entity) const { + ENTT_ASSERT(valid(entity), "Invalid entity"); + + if constexpr(sizeof...(Component) == 1) { + const auto &cpool = assure...>(); + return cpool.contains(entity) ? std::addressof(cpool.get(entity)) : nullptr; } else { - for(const auto entity: static_cast &>(*cpool)) { - cpool->remove(*this, entity); - } + return std::make_tuple(try_get(entity)...); + } + } + + /*! @copydoc try_get */ + template + [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entity) { + if constexpr(sizeof...(Component) == 1) { + return (const_cast(std::as_const(*this).template try_get(entity)), ...); + } else { + return std::make_tuple(try_get(entity)...); } } /** - * @brief Resets a whole registry. - * - * Destroys all the entities. After a call to `reset`, all the entities - * still in use are recycled with a new version number. In case entity - * identifers are stored around, the `valid` member function can be used - * to know if they are still valid. + * @brief Clears a whole registry or the pools for the given components. + * @tparam Component Types of components to remove from their entities. */ - void reset() { - each([this](const auto entity) { - // useless this-> used to suppress a warning with clang - this->destroy(entity); - }); + template + void clear() { + if constexpr(sizeof...(Component) == 0) { + for(auto &&curr: pools) { + curr.second->clear(); + } + + each([this](const auto entity) { this->release(entity); }); + } else { + (assure().clear(), ...); + } } /** * @brief Iterates all the entities that are still in use. * - * The function object is invoked for each entity that is still in use.
* The signature of the function should be equivalent to the following: * * @code{.cpp} * void(const Entity); * @endcode * - * This function is fairly slow and should not be used frequently. However, - * it's useful for iterating all the entities still in use, regardless of - * their components. + * It's not defined whether entities created during iteration are returned. * * @tparam Func Type of the function object to invoke. * @param func A valid function object. */ template void each(Func func) const { - static_assert(std::is_invocable_v); - - if(available) { + if(free_list == null) { for(auto pos = entities.size(); pos; --pos) { - const auto curr = entity_type(pos - 1); - const auto entity = entities[curr]; - const auto entt = entity & traits_type::entity_mask; - - if(curr == entt) { - func(entity); - } + func(entities[pos - 1]); } } else { for(auto pos = entities.size(); pos; --pos) { - func(entities[pos-1]); + if(const auto entity = entities[pos - 1]; entity_traits::to_entity(entity) == (pos - 1)) { + func(entity); + } } } } /** * @brief Checks if an entity has components assigned. - * @param entity A valid entity identifier. + * @param entity A valid identifier. * @return True if the entity has no components assigned, false otherwise. */ - bool orphan(const entity_type entity) const { - ENTT_ASSERT(valid(entity)); - bool orphan = true; - - for(std::size_t pos{}, last = pools.size(); pos < last && orphan; ++pos) { - const auto &pdata = pools[pos]; - orphan = !(pdata.pool && pdata.pool->has(entity)); - } - - return orphan; + [[nodiscard]] bool orphan(const entity_type entity) const { + ENTT_ASSERT(valid(entity), "Invalid entity"); + return std::none_of(pools.cbegin(), pools.cend(), [entity](auto &&curr) { return curr.second->contains(entity); }); } /** - * @brief Iterates orphans and applies them the given function object. + * @brief Returns a sink object for the given component. * - * The function object is invoked for each entity that is still in use and - * has no components assigned.
- * The signature of the function should be equivalent to the following: + * Use this function to receive notifications whenever a new instance of the + * given component is created and assigned to an entity.
+ * The function type for a listener is equivalent to: * * @code{.cpp} - * void(const Entity); + * void(basic_registry &, Entity); * @endcode * - * This function can be very slow and should not be used frequently. + * Listeners are invoked **after** assigning the component to the entity. * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. + * @sa sink + * + * @tparam Component Type of component of which to get the sink. + * @return A temporary sink object. */ - template - void orphans(Func func) const { - static_assert(std::is_invocable_v); - - each([&func, this](const auto entity) { - if(orphan(entity)) { - func(entity); - } - }); + template + [[nodiscard]] auto on_construct() { + return assure().on_construct(); } /** - * @brief Returns a view for the given components. - * - * This kind of objects are created on the fly and share with the registry - * its internal data structures.
- * Feel free to discard a view after the use. Creating and destroying a view - * is an incredibly cheap operation because they do not require any type of - * initialization.
- * As a rule of thumb, storing a view should never be an option. + * @brief Returns a sink object for the given component. * - * Views do their best to iterate the smallest set of candidate entities. - * In particular: + * Use this function to receive notifications whenever an instance of the + * given component is explicitly updated.
+ * The function type for a listener is equivalent to: * - * * Single component views are incredibly fast and iterate a packed array - * of entities, all of which has the given component. - * * Multi component views look at the number of entities available for each - * component and pick up a reference to the smallest set of candidates to - * test for the given components. + * @code{.cpp} + * void(basic_registry &, Entity); + * @endcode * - * Views in no way affect the functionalities of the registry nor those of - * the underlying pools. + * Listeners are invoked **after** updating the component. * - * @note - * Multi component views are pretty fast. However their performance tend to - * degenerate when the number of components to iterate grows up and the most - * of the entities have all the given components.
- * To get a performance boost, consider using a group instead. + * @sa sink * - * @tparam Component Type of components used to construct the view. - * @return A newly created view. + * @tparam Component Type of component of which to get the sink. + * @return A temporary sink object. */ - template - entt::basic_view view() { - return { assure()... }; + template + [[nodiscard]] auto on_update() { + return assure().on_update(); } - /*! @copydoc view */ - template - inline entt::basic_view view() const { - static_assert(std::conjunction_v...>); - return const_cast(this)->view(); + /** + * @brief Returns a sink object for the given component. + * + * Use this function to receive notifications whenever an instance of the + * given component 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 component from the entity. + * + * @sa sink + * + * @tparam Component Type of component of which to get the sink. + * @return A temporary sink object. + */ + template + [[nodiscard]] auto on_destroy() { + return assure().on_destroy(); } /** - * @brief Checks whether a given component belongs to a group. - * @tparam Component Type of component in which one is interested. - * @return True if the component belongs to a group, false otherwise. + * @brief Returns a view for the given components. + * + * Views are created on the fly and share with the registry its internal + * data structures. Feel free to discard them after the use.
+ * Creating and destroying a view is an incredibly cheap operation. As a + * rule of thumb, storing a view should never be an option. + * + * @tparam Component Type of component used to construct the view. + * @tparam Other Other types of components used to construct the view. + * @tparam Exclude Types of components used to filter the view. + * @return A newly created view. */ - template - bool owned() const ENTT_NOEXCEPT { - const auto *cpool = pool(); - return cpool && cpool->group; + template + [[nodiscard]] basic_view, std::add_const_t...>, exclude_t> view(exclude_t = {}) const { + return {assure>(), assure>()..., assure()...}; + } + + /*! @copydoc view */ + template + [[nodiscard]] basic_view, exclude_t> view(exclude_t = {}) { + return {assure>(), assure>()..., assure()...}; } /** * @brief Returns a group for the given components. * - * This kind of objects are created on the fly and share with the registry - * its internal data structures.
- * Feel free to discard a group after the use. Creating and destroying a - * group is an incredibly cheap operation because they do not require any - * type of initialization, but for the first time they are requested.
- * As a rule of thumb, storing a group should never be an option. + * Groups are created on the fly and share with the registry its internal + * data structures. Feel free to discard them after the use.
+ * Creating and destroying a group is an incredibly cheap operation. As a + * rule of thumb, storing a group should never be an option. * * Groups support exclusion lists and can own types of components. The more * types are owned by a group, the faster it is to iterate entities and * components.
* However, groups also affect some features of the registry such as the - * creation and destruction of components, which will consequently be - * slightly slower (nothing that can be noticed in most cases). + * creation and destruction of components. * * @note * Pools of components that are owned by a group cannot be sorted anymore. @@ -1320,380 +1261,235 @@ public: * @return A newly created group. */ template - inline entt::basic_group, Owned...> group(get_t, exclude_t = {}) { - static_assert(sizeof...(Owned) + sizeof...(Get) > 0); - static_assert(sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude) > 1); + [[nodiscard]] basic_group, get_t, exclude_t> group(get_t, exclude_t = {}) { + static_assert(sizeof...(Owned) + sizeof...(Get) > 0, "Exclusion-only groups are not supported"); + static_assert(sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude) > 1, "Single component groups are not allowed"); - using handler_type = group_handler, type_list, Owned...>; + using handler_type = group_handler...>, get_t...>, std::remove_const_t...>; - const std::size_t extent[] = { sizeof...(Owned), sizeof...(Get), sizeof...(Exclude) }; - const ENTT_ID_TYPE types[] = { type()..., type()..., type()... }; - handler_type *curr = nullptr; + const auto cpools = std::forward_as_tuple(assure>()..., assure>()...); + constexpr auto size = sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude); + handler_type *handler = nullptr; - if(auto it = std::find_if(groups.begin(), groups.end(), [&extent, &types](auto &&gdata) { - return std::equal(std::begin(extent), std::end(extent), gdata.extent) && gdata.is_same(types); - }); it != groups.cend()) - { - curr = static_cast(it->group.get()); - } + auto it = std::find_if(groups.cbegin(), groups.cend(), [size](const auto &gdata) { + return gdata.size == size + && (gdata.owned(type_hash>::value()) && ...) + && (gdata.get(type_hash>::value()) && ...) + && (gdata.exclude(type_hash::value()) && ...); + }); + + if(it != groups.cend()) { + handler = static_cast(it->group.get()); + } else { + group_data candidate = { + size, + {new handler_type{}, [](void *instance) { delete static_cast(instance); }}, + []([[maybe_unused]] const id_type ctype) ENTT_NOEXCEPT { return ((ctype == type_hash>::value()) || ...); }, + []([[maybe_unused]] const id_type ctype) ENTT_NOEXCEPT { return ((ctype == type_hash>::value()) || ...); }, + []([[maybe_unused]] const id_type ctype) ENTT_NOEXCEPT { return ((ctype == type_hash::value()) || ...); }, + }; - if(!curr) { - ENTT_ASSERT(!(owned() || ...)); + handler = static_cast(candidate.group.get()); + + const void *maybe_valid_if = nullptr; + const void *discard_if = nullptr; + + if constexpr(sizeof...(Owned) == 0) { + groups.push_back(std::move(candidate)); + } else { + [[maybe_unused]] auto has_conflict = [size](const auto &gdata) { + const auto overlapping = (0u + ... + gdata.owned(type_hash>::value())); + const auto sz = overlapping + (0u + ... + gdata.get(type_hash>::value())) + (0u + ... + gdata.exclude(type_hash::value())); + return !overlapping || ((sz == size) || (sz == gdata.size)); + }; - groups.push_back(group_data{ - { sizeof...(Owned), sizeof...(Get), sizeof...(Exclude) }, - decltype(group_data::group){new handler_type{}, +[](void *gptr) { delete static_cast(gptr); }}, - +[](const ENTT_ID_TYPE *other) { - const std::size_t ctypes[] = { type()..., type()..., type()... }; - return std::equal(std::begin(ctypes), std::end(ctypes), other); + ENTT_ASSERT(std::all_of(groups.cbegin(), groups.cend(), std::move(has_conflict)), "Conflicting groups"); + + const auto next = std::find_if_not(groups.cbegin(), groups.cend(), [size](const auto &gdata) { + return !(0u + ... + gdata.owned(type_hash>::value())) || (size > gdata.size); + }); + + const auto prev = std::find_if(std::make_reverse_iterator(next), groups.crend(), [](const auto &gdata) { + return (0u + ... + gdata.owned(type_hash>::value())); + }); + + maybe_valid_if = (next == groups.cend() ? maybe_valid_if : next->group.get()); + discard_if = (prev == groups.crend() ? discard_if : prev->group.get()); + groups.insert(next, std::move(candidate)); + } + + (on_construct>().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if>>(*handler), ...); + (on_construct>().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if>>(*handler), ...); + (on_destroy().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if>(*handler), ...); + + (on_destroy>().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...); + (on_destroy>().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...); + (on_construct().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...); + + if constexpr(sizeof...(Owned) == 0) { + for(const auto entity: view(exclude)) { + handler->current.emplace(entity); } - }); - - const auto cpools = std::make_tuple(assure()..., assure()..., assure()...); - curr = static_cast(groups.back().group.get()); - - ((std::get *>(cpools)->group = curr), ...); - (std::get *>(cpools)->on_construct.sink().template connect<&handler_type::template maybe_valid_if>(curr), ...); - (std::get *>(cpools)->on_destroy.sink().template connect<&handler_type::template discard_if<>>(curr), ...); - - (std::get *>(cpools)->on_construct.sink().template connect<&handler_type::template maybe_valid_if>(curr), ...); - (std::get *>(cpools)->on_destroy.sink().template connect<&handler_type::template discard_if<>>(curr), ...); - - (std::get *>(cpools)->on_destroy.sink().template connect<&handler_type::template maybe_valid_if>(curr), ...); - (std::get *>(cpools)->on_construct.sink().template connect<&handler_type::template discard_if>(curr), ...); - - const auto *cpool = std::min({ - static_cast *>(std::get *>(cpools))..., - static_cast *>(std::get *>(cpools))... - }, [](const auto *lhs, const auto *rhs) { - return lhs->size() < rhs->size(); - }); - - // we cannot iterate backwards because we want to leave behind valid entities in case of owned types - std::for_each(cpool->data(), cpool->data() + cpool->size(), [curr, &cpools](const auto entity) { - if((std::get *>(cpools)->has(entity) && ...) - && (std::get *>(cpools)->has(entity) && ...) - && !(std::get *>(cpools)->has(entity) || ...)) - { - if constexpr(sizeof...(Owned) == 0) { - curr->construct(entity); - } else { - const auto pos = curr->owned++; - // useless this-> used to suppress a warning with clang - (std::get *>(cpools)->swap(std::get *>(cpools)->sparse_set::get(entity), pos), ...); - } + } else { + // we cannot iterate backwards because we want to leave behind valid entities in case of owned types + for(auto *first = std::get<0>(cpools).data(), *last = first + std::get<0>(cpools).size(); first != last; ++first) { + handler->template maybe_valid_if...>>>(*this, *first); } - }); + } } - if constexpr(sizeof...(Owned) == 0) { - return { static_cast *>(curr), pool()... }; - } else { - return { &curr->owned, pool()... , pool()... }; - } + return {handler->current, std::get> &>(cpools)..., std::get> &>(cpools)...}; } /*! @copydoc group */ template - inline entt::basic_group, Owned...> group(get_t, exclude_t = {}) const { - static_assert(std::conjunction_v..., std::is_const...>); - return const_cast(this)->group(entt::get, exclude); + [[nodiscard]] basic_group...>, get_t...>, exclude_t> group_if_exists(get_t, exclude_t = {}) const { + auto it = std::find_if(groups.cbegin(), groups.cend(), [](const auto &gdata) { + return gdata.size == (sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude)) + && (gdata.owned(type_hash>::value()) && ...) + && (gdata.get(type_hash>::value()) && ...) + && (gdata.exclude(type_hash::value()) && ...); + }); + + if(it == groups.cend()) { + return {}; + } else { + using handler_type = group_handler...>, get_t...>, std::remove_const_t...>; + return {static_cast(it->group.get())->current, assure>()..., assure>()...}; + } } /*! @copydoc group */ template - inline entt::basic_group, Owned...> group(exclude_t = {}) { - return group(entt::get<>, exclude); + [[nodiscard]] basic_group, get_t<>, exclude_t> group(exclude_t = {}) { + return group(get_t<>{}, exclude); } /*! @copydoc group */ template - inline entt::basic_group, Owned...> group(exclude_t = {}) const { - static_assert(std::conjunction_v...>); - return const_cast(this)->group(exclude); + [[nodiscard]] basic_group...>, get_t<>, exclude_t> group_if_exists(exclude_t = {}) const { + return group_if_exists...>(get_t<>{}, exclude); } /** - * @brief Returns a runtime view for the given components. - * - * This kind of objects are created on the fly and share with the registry - * its internal data structures.
- * Users should throw away the view after use. Fortunately, creating and - * destroying a runtime view is an incredibly cheap operation because they - * do not require any type of initialization.
- * As a rule of thumb, storing a view should never be an option. - * - * Runtime views are to be used when users want to construct a view from - * some external inputs and don't know at compile-time what are the required - * components.
- * This is particularly well suited to plugin systems and mods in general. - * - * @tparam It Type of forward iterator. - * @param first An iterator to the first element of the range of components. - * @param last An iterator past the last element of the range of components. - * @return A newly created runtime view. + * @brief Checks whether the given components belong to any group. + * @tparam Component Types of components in which one is interested. + * @return True if the pools of the given components are _free_, false + * otherwise. */ - template - entt::basic_runtime_view runtime_view(It first, It last) const { - static_assert(std::is_convertible_v::value_type, component_type>); - std::vector *> set(std::distance(first, last)); - - std::transform(first, last, set.begin(), [this](const component_type ctype) { - auto it = std::find_if(pools.begin(), pools.end(), [ctype](const auto &pdata) { - return pdata.pool && pdata.runtime_type == ctype; - }); - - return it != pools.cend() && it->pool ? it->pool.get() : nullptr; - }); - - return { std::move(set) }; + template + [[nodiscard]] bool owned() const { + return std::any_of(groups.cbegin(), groups.cend(), [](auto &&gdata) { return (gdata.owned(type_hash>::value()) || ...); }); } /** - * @brief Clones the given components and all the entity identifiers. - * - * The components must be copiable for obvious reasons. The entities - * maintain their versions once copied.
- * If no components are provided, the registry will try to clone all the - * existing pools. - * - * @note - * There isn't an efficient way to know if all the entities are assigned at - * least one component once copied. Therefore, there may be orphans. It is - * up to the caller to clean up the registry if necessary. - * - * @note - * Listeners and groups aren't copied. It is up to the caller to connect the - * listeners of interest to the new registry and to set up groups. - * - * @warning - * Attempting to clone components that aren't copyable results in unexpected - * behaviors.
- * A static assertion will abort the compilation when the components - * provided aren't copy constructible. Otherwise, an assertion will abort - * the execution at runtime in debug mode in case one or more pools cannot - * be cloned. - * - * @tparam Component Types of components to clone. - * @return A fresh copy of the registry. + * @brief Checks whether a group can be sorted. + * @tparam Owned Types of components owned by the group. + * @tparam Get Types of components observed by the group. + * @tparam Exclude Types of components used to filter the group. + * @return True if the group can be sorted, false otherwise. */ - template - basic_registry clone() const { - static_assert(std::conjunction_v...>); - basic_registry other; - - other.pools.resize(pools.size()); - - for(auto pos = pools.size(); pos; --pos) { - if(auto &pdata = pools[pos-1]; pdata.pool && (!sizeof...(Component) || ... || (pdata.runtime_type == type()))) { - auto &curr = other.pools[pos-1]; - curr.clone = pdata.clone; - curr.pool = curr.clone(*pdata.pool); - curr.runtime_type = pdata.runtime_type; - ENTT_ASSERT(sizeof...(Component) == 0 || curr.pool); - } - } - - other.skip_family_pools = skip_family_pools; - other.entities = entities; - other.available = available; - other.next = next; - - other.pools.erase(std::remove_if(other.pools.begin()+skip_family_pools, other.pools.end(), [](const auto &pdata) { - return !pdata.pool; - }), other.pools.end()); - - return other; + template + [[nodiscard]] bool sortable(const basic_group, get_t, exclude_t> &) ENTT_NOEXCEPT { + constexpr auto size = sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude); + auto pred = [size](const auto &gdata) { return (0u + ... + gdata.owned(type_hash>::value())) && (size < gdata.size); }; + return std::find_if(groups.cbegin(), groups.cend(), std::move(pred)) == groups.cend(); } /** - * @brief Returns a temporary object to use to create snapshots. + * @brief Sorts the elements of a given component. * - * A snapshot is either a full or a partial dump of a registry.
- * It can be used to save and restore its internal state or to keep two or - * more instances of this class in sync, as an example in a client-server - * architecture. + * The order remains valid until a component of the given type is assigned + * to or removed from an entity.
+ * 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: * - * @return A temporary object to use to take snasphosts. - */ - entt::basic_snapshot snapshot() const ENTT_NOEXCEPT { - using follow_fn_type = entity_type(const basic_registry &, const entity_type); - const entity_type seed = available ? (next | (entities[next] & (traits_type::version_mask << traits_type::entity_shift))) : next; - - follow_fn_type *follow = [](const basic_registry ®, const entity_type entity) -> entity_type { - const auto &others = reg.entities; - const auto entt = entity & traits_type::entity_mask; - const auto curr = others[entt] & traits_type::entity_mask; - return (curr | (others[curr] & (traits_type::version_mask << traits_type::entity_shift))); - }; - - return { this, seed, follow }; - } - - /** - * @brief Returns a temporary object to use to load snapshots. + * @code{.cpp} + * bool(const Entity, const Entity); + * bool(const Component &, const Component &); + * @endcode * - * A snapshot is either a full or a partial dump of a registry.
- * It can be used to save and restore its internal state or to keep two or - * more instances of this class in sync, as an example in a client-server - * architecture. + * Moreover, it shall induce a _strict weak ordering_ on the values.
+ * The sort function object offers an `operator()` that accepts: * - * @note - * The loader returned by this function requires that the registry be empty. - * In case it isn't, all the data will be automatically deleted before to - * return. + * * 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. * - * @return A temporary object to use to load snasphosts. - */ - basic_snapshot_loader loader() ENTT_NOEXCEPT { - using force_fn_type = void(basic_registry &, const entity_type, const bool); - - force_fn_type *force = [](basic_registry ®, const entity_type entity, const bool destroyed) { - using promotion_type = std::conditional_t= sizeof(entity_type), size_type, entity_type>; - // explicit promotion to avoid warnings with std::uint16_t - const auto entt = promotion_type{entity} & traits_type::entity_mask; - auto &others = reg.entities; - - if(!(entt < others.size())) { - auto curr = others.size(); - others.resize(entt + 1); - std::iota(others.data() + curr, others.data() + entt, entity_type(curr)); - } - - others[entt] = entity; - - if(destroyed) { - reg.destroy(entity); - const auto version = entity & (traits_type::version_mask << traits_type::entity_shift); - others[entt] = ((others[entt] & traits_type::entity_mask) | version); - } - }; - - reset(); - entities.clear(); - available = {}; - - return { this, force }; - } - - /** - * @brief Binds an object to the context of the registry. + * The comparison function object hasn't necessarily the type of the one + * passed along with the other parameters to this member function. * - * If the value already exists it is overwritten, otherwise a new instance - * of the given type is created and initialized with the arguments provided. + * @warning + * Pools of components owned by a group cannot be sorted. * - * @tparam Type Type of object to set. - * @tparam Args Types of arguments to use to construct the object. - * @param args Parameters to use to initialize the value. - * @return A reference to the newly created object. + * @tparam Component Type of components 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 - Type & set(Args &&... args) { - const auto ctype = runtime_type(); - auto it = std::find_if(vars.begin(), vars.end(), [ctype](const auto &candidate) { - return candidate.runtime_type == ctype; - }); - - if(it == vars.cend()) { - vars.push_back({ - decltype(ctx_variable::value){new Type{std::forward(args)...}, +[](void *ptr) { delete static_cast(ptr); }}, - ctype - }); + template + void sort(Compare compare, Sort algo = Sort{}, Args &&...args) { + ENTT_ASSERT(!owned(), "Cannot sort owned storage"); + auto &cpool = assure(); - it = std::prev(vars.end()); + if constexpr(std::is_invocable_v) { + auto comp = [&cpool, compare = std::move(compare)](const auto lhs, const auto rhs) { return compare(std::as_const(cpool.get(lhs)), std::as_const(cpool.get(rhs))); }; + cpool.sort(std::move(comp), std::move(algo), std::forward(args)...); } else { - it->value.reset(new Type{std::forward(args)...}); + cpool.sort(std::move(compare), std::move(algo), std::forward(args)...); } - - return *static_cast(it->value.get()); } /** - * @brief Unsets a context variable if it exists. - * @tparam Type Type of object to set. - */ - template - void unset() { - vars.erase(std::remove_if(vars.begin(), vars.end(), [](auto &var) { - return var.runtime_type == runtime_type(); - }), vars.end()); - } - - /** - * @brief Binds an object to the context of the registry. + * @brief Sorts two pools of components in the same way. * - * In case the context doesn't contain the given object, the parameters - * provided are used to construct it. + * Being `To` and `From` the two sets, after invoking this function an + * iterator for `To` returns elements according to the following rules: * - * @tparam Type Type of object to set. - * @tparam Args Types of arguments to use to construct the object. - * @param args Parameters to use to initialize the object. - * @return Reference to the object. - */ - template - Type & ctx_or_set(Args &&... args) { - auto *type = try_ctx(); - return type ? *type : set(std::forward(args)...); - } - - /** - * @brief Returns a pointer to an object in the context of the registry. - * @tparam Type Type of object to get. - * @return A pointer to the object if it exists in the context of the - * registry, a null pointer otherwise. + * * All entities in `To` that are also in `From` are returned first + * according to the order they have in `From`. + * * All entities in `To` that are not in `From` are returned in no + * particular order after all the other entities. + * + * Any subsequent change to `From` won't affect the order in `To`. + * + * @warning + * Pools of components owned by a group cannot be sorted. + * + * @tparam To Type of components to sort. + * @tparam From Type of components to use to sort. */ - template - const Type * try_ctx() const ENTT_NOEXCEPT { - const auto it = std::find_if(vars.begin(), vars.end(), [](const auto &var) { - return var.runtime_type == runtime_type(); - }); - - return (it == vars.cend()) ? nullptr : static_cast(it->value.get()); - } - - /*! @copydoc try_ctx */ - template - inline Type * try_ctx() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).template try_ctx()); + template + void sort() { + ENTT_ASSERT(!owned(), "Cannot sort owned storage"); + assure().respect(assure()); } /** - * @brief Returns a reference to an object in the context of the registry. - * - * @warning - * Attempting to get a context variable that doesn't exist results in - * undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid requests. - * - * @tparam Type Type of object to get. - * @return A valid reference to the object in the context of the registry. + * @brief Returns the context object, that is, a general purpose container. + * @return The context object, that is, a general purpose container. */ - template - const Type & ctx() const ENTT_NOEXCEPT { - const auto *instance = try_ctx(); - ENTT_ASSERT(instance); - return *instance; + context &ctx() ENTT_NOEXCEPT { + return vars; } /*! @copydoc ctx */ - template - inline Type & ctx() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).template ctx()); + const context &ctx() const ENTT_NOEXCEPT { + return vars; } private: - std::size_t skip_family_pools{}; - std::vector pools; + dense_map, identity> pools; std::vector groups; - std::vector vars; std::vector entities; - size_type available{}; - entity_type next{}; + entity_type free_list; + context vars; }; +} // namespace entt -} - - -#endif // ENTT_ENTITY_REGISTRY_HPP +#endif diff --git a/modules/entt/src/entt/entity/runtime_view.hpp b/modules/entt/src/entt/entity/runtime_view.hpp index 8507c81..4baaa61 100644 --- a/modules/entt/src/entt/entity/runtime_view.hpp +++ b/modules/entt/src/entt/entity/runtime_view.hpp @@ -1,23 +1,119 @@ #ifndef ENTT_ENTITY_RUNTIME_VIEW_HPP #define ENTT_ENTITY_RUNTIME_VIEW_HPP - +#include #include -#include -#include +#include #include -#include +#include #include "../config/config.h" -#include "sparse_set.hpp" #include "entity.hpp" #include "fwd.hpp" - +#include "sparse_set.hpp" namespace entt { +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +class runtime_view_iterator final { + using iterator_type = typename Set::iterator; + + [[nodiscard]] bool valid() const { + return (!tombstone_check || *it != tombstone) + && std::all_of(++pools->begin(), pools->end(), [entt = *it](const auto *curr) { return curr->contains(entt); }) + && std::none_of(filter->cbegin(), filter->cend(), [entt = *it](const auto *curr) { return curr && curr->contains(entt); }); + } + +public: + using difference_type = typename iterator_type::difference_type; + using value_type = typename iterator_type::value_type; + using pointer = typename iterator_type::pointer; + using reference = typename iterator_type::reference; + using iterator_category = std::bidirectional_iterator_tag; + + runtime_view_iterator() ENTT_NOEXCEPT + : pools{}, + filter{}, + it{}, + tombstone_check{} {} + + runtime_view_iterator(const std::vector &cpools, const std::vector &ignore, iterator_type curr) ENTT_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++() { + while(++it != (*pools)[0]->end() && !valid()) {} + return *this; + } + + runtime_view_iterator operator++(int) { + runtime_view_iterator orig = *this; + return ++(*this), orig; + } + + runtime_view_iterator &operator--() { + while(--it != (*pools)[0]->begin() && !valid()) {} + return *this; + } + + runtime_view_iterator operator--(int) { + runtime_view_iterator orig = *this; + return operator--(), orig; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return it.operator->(); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return *operator->(); + } + + [[nodiscard]] bool operator==(const runtime_view_iterator &other) const ENTT_NOEXCEPT { + return it == other.it; + } + + [[nodiscard]] bool operator!=(const runtime_view_iterator &other) const ENTT_NOEXCEPT { + return !(*this == other); + } + +private: + const std::vector *pools; + const std::vector *filter; + iterator_type it; + bool tombstone_check; +}; + +} // namespace internal /** - * @brief Runtime view. + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Runtime view implementation. + * + * Primary template isn't defined on purpose. All the specializations give a + * compile-time error, but for a few reasonable cases. + */ +template +struct basic_runtime_view; + +/** + * @brief Generic runtime view. * * Runtime views iterate over those entities that have at least all the given * components in their bags. During initialization, a runtime view looks at the @@ -35,6 +131,7 @@ namespace entt { * * New instances of the given components are created and assigned to entities. * * The entity currently pointed is modified (as an example, if one of the * given components is removed from the entity to which the iterator points). + * * The entity currently pointed is destroyed. * * In all the other cases, modifying the pools of the given components in any * way invalidates all the iterators and using them results in undefined @@ -48,137 +145,59 @@ namespace entt { * have a valid reference and won't be updated accordingly). * * @warning - * Lifetime of a view must overcome the one of the registry that generated it. + * Lifetime of a view must not overcome that of the registry that generated it. * In any other case, attempting to use a view results in undefined behavior. * * @tparam Entity A valid entity type (see entt_traits for more details). + * @tparam Allocator Type of allocator used to manage memory and elements. */ -template -class basic_runtime_view { - /*! @brief A registry is allowed to create views. */ - friend class basic_registry; - - using underlying_iterator_type = typename sparse_set::iterator_type; - using extent_type = typename sparse_set::size_type; - using traits_type = entt_traits; - - class iterator { - friend class basic_runtime_view; - - iterator(underlying_iterator_type first, underlying_iterator_type last, const sparse_set * const *others, const sparse_set * const *length, extent_type ext) ENTT_NOEXCEPT - : begin{first}, - end{last}, - from{others}, - to{length}, - extent{ext} - { - if(begin != end && !valid()) { - ++(*this); - } - } - - bool valid() const ENTT_NOEXCEPT { - const auto entt = *begin; - const auto sz = size_type(entt & traits_type::entity_mask); - - return sz < extent && std::all_of(from, to, [entt](const auto *view) { - return view->has(entt); - }); - } - - public: - using difference_type = typename underlying_iterator_type::difference_type; - using value_type = typename underlying_iterator_type::value_type; - using pointer = typename underlying_iterator_type::pointer; - using reference = typename underlying_iterator_type::reference; - using iterator_category = std::forward_iterator_tag; - - iterator() ENTT_NOEXCEPT = default; - - iterator & operator++() ENTT_NOEXCEPT { - return (++begin != end && !valid()) ? ++(*this) : *this; - } - - iterator operator++(int) ENTT_NOEXCEPT { - iterator orig = *this; - return ++(*this), orig; - } - - bool operator==(const iterator &other) const ENTT_NOEXCEPT { - return other.begin == begin; - } - - inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this == other); - } - - pointer operator->() const ENTT_NOEXCEPT { - return begin.operator->(); - } - - inline reference operator*() const ENTT_NOEXCEPT { - return *operator->(); - } - - private: - underlying_iterator_type begin; - underlying_iterator_type end; - const sparse_set * const *from; - const sparse_set * const *to; - extent_type extent; - }; - - basic_runtime_view(std::vector *> others) ENTT_NOEXCEPT - : pools{std::move(others)} - { - const auto it = std::min_element(pools.begin(), pools.end(), [](const auto *lhs, const auto *rhs) { - return (!lhs && rhs) || (lhs && rhs && lhs->size() < rhs->size()); - }); - - // brings the best candidate (if any) on front of the vector - std::rotate(pools.begin(), it, pools.end()); - } - - extent_type min() const ENTT_NOEXCEPT { - extent_type extent{}; +template +struct basic_runtime_view> { + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using base_type = basic_sparse_set; + /*! @brief Bidirectional iterator type. */ + using iterator = internal::runtime_view_iterator; - if(valid()) { - const auto it = std::min_element(pools.cbegin(), pools.cend(), [](const auto *lhs, const auto *rhs) { - return lhs->extent() < rhs->extent(); - }); + /*! @brief Default constructor to use to create empty, invalid views. */ + basic_runtime_view() ENTT_NOEXCEPT + : pools{}, + filter{} {} - extent = (*it)->extent(); + /** + * @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(const base_type &base) { + if(pools.empty() || !(base.size() < pools[0u]->size())) { + pools.push_back(&base); + } else { + pools.push_back(std::exchange(pools[0u], &base)); } - return extent; - } - - inline bool valid() const ENTT_NOEXCEPT { - return !pools.empty() && pools.front(); + return *this; } -public: - /*! @brief Underlying entity identifier. */ - using entity_type = typename sparse_set::entity_type; - /*! @brief Unsigned integer type. */ - using size_type = typename sparse_set::size_type; - /*! @brief Input iterator type. */ - using iterator_type = iterator; - /** - * @brief Estimates the number of entities that have the given components. - * @return Estimated number of entities that have the given components. + * @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. */ - size_type size() const ENTT_NOEXCEPT { - return valid() ? pools.front()->size() : size_type{}; + basic_runtime_view &exclude(const base_type &base) { + filter.push_back(&base); + return *this; } /** - * @brief Checks if the view is definitely empty. - * @return True if the view is definitely empty, false otherwise. + * @brief Estimates the number of entities iterated by the view. + * @return Estimated number of entities iterated by the view. */ - bool empty() const ENTT_NOEXCEPT { - return !valid() || pools.front()->empty(); + [[nodiscard]] size_type size_hint() const { + return pools.empty() ? size_type{} : pools.front()->size(); } /** @@ -189,22 +208,10 @@ public: * components. If the view is empty, the returned iterator will be equal to * `end()`. * - * @note - * Input iterators stay true to the order imposed to the underlying data - * structures. - * * @return An iterator to the first entity that has the given components. */ - iterator_type begin() const ENTT_NOEXCEPT { - iterator_type it{}; - - if(valid()) { - const auto &pool = *pools.front(); - const auto * const *data = pools.data(); - it = { pool.begin(), pool.end(), data + 1, data + pools.size(), min() }; - } - - return it; + [[nodiscard]] iterator begin() const { + return pools.empty() ? iterator{} : iterator{pools, filter, pools[0]->begin()}; } /** @@ -215,33 +222,22 @@ public: * has the given components. Attempting to dereference the returned iterator * results in undefined behavior. * - * @note - * Input iterators stay true to the order imposed to the underlying data - * structures. - * * @return An iterator to the entity following the last entity that has the * given components. */ - iterator_type end() const ENTT_NOEXCEPT { - iterator_type it{}; - - if(valid()) { - const auto &pool = *pools.front(); - it = { pool.end(), pool.end(), nullptr, nullptr, min() }; - } - - return it; + [[nodiscard]] iterator end() const { + return pools.empty() ? iterator{} : iterator{pools, filter, pools[0]->end()}; } /** * @brief Checks if a view contains an entity. - * @param entt A valid entity identifier. + * @param entt A valid identifier. * @return True if the view contains the given entity, false otherwise. */ - bool contains(const entity_type entt) const ENTT_NOEXCEPT { - return valid() && std::all_of(pools.cbegin(), pools.cend(), [entt](const auto *view) { - return view->has(entt) && view->data()[view->get(entt)] == entt; - }); + [[nodiscard]] bool contains(const entity_type entt) const { + return !pools.empty() + && std::all_of(pools.cbegin(), pools.cend(), [entt](const auto *curr) { return curr->contains(entt); }) + && std::none_of(filter.cbegin(), filter.cend(), [entt](const auto *curr) { return curr && curr->contains(entt); }); } /** @@ -261,15 +257,16 @@ public: */ template void each(Func func) const { - std::for_each(begin(), end(), func); + for(const auto entity: *this) { + func(entity); + } } private: - std::vector *> pools; + std::vector pools; + std::vector filter; }; +} // namespace entt -} - - -#endif // ENTT_ENTITY_RUNTIME_VIEW_HPP +#endif diff --git a/modules/entt/src/entt/entity/sigh_storage_mixin.hpp b/modules/entt/src/entt/entity/sigh_storage_mixin.hpp new file mode 100644 index 0000000..39e7226 --- /dev/null +++ b/modules/entt/src/entt/entity/sigh_storage_mixin.hpp @@ -0,0 +1,177 @@ +#ifndef ENTT_ENTITY_SIGH_STORAGE_MIXIN_HPP +#define ENTT_ENTITY_SIGH_STORAGE_MIXIN_HPP + +#include +#include "../config/config.h" +#include "../core/any.hpp" +#include "../signal/sigh.hpp" +#include "fwd.hpp" + +namespace entt { + +/** + * @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 The type of the underlying storage. + */ +template +class sigh_storage_mixin final: public Type { + using basic_iterator = typename Type::basic_iterator; + + template + void notify_destruction(basic_iterator first, basic_iterator last, Func func) { + ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); + + for(; first != last; ++first) { + const auto entt = *first; + destruction.publish(*owner, entt); + const auto it = Type::find(entt); + func(it, it + 1u); + } + } + + void swap_and_pop(basic_iterator first, basic_iterator last) final { + notify_destruction(std::move(first), std::move(last), [this](auto... args) { Type::swap_and_pop(args...); }); + } + + void in_place_pop(basic_iterator first, basic_iterator last) final { + notify_destruction(std::move(first), std::move(last), [this](auto... args) { Type::in_place_pop(args...); }); + } + + basic_iterator try_emplace(const typename Type::entity_type entt, const bool force_back, const void *value) final { + ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); + Type::try_emplace(entt, force_back, value); + construction.publish(*owner, entt); + return Type::find(entt); + } + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = typename Type::entity_type; + + /*! @brief Inherited constructors. */ + using Type::Type; + + /** + * @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() ENTT_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() ENTT_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() ENTT_NOEXCEPT { + return sink{destruction}; + } + + /** + * @brief Assigns entities to a storage. + * @tparam Args Types of arguments to use to construct the object. + * @param entt A valid identifier. + * @param args Parameters to use to initialize the object. + * @return A reference to the newly created object. + */ + template + decltype(auto) emplace(const entity_type entt, Args &&...args) { + ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); + Type::emplace(entt, std::forward(args)...); + construction.publish(*owner, entt); + return this->get(entt); + } + + /** + * @brief Patches the given instance for an entity. + * @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) { + ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); + Type::patch(entt, std::forward(func)...); + update.publish(*owner, entt); + return this->get(entt); + } + + /** + * @brief Assigns entities to a storage. + * @tparam It Type of input iterator. + * @tparam Args Types of arguments to use to construct the objects assigned + * to the entities. + * @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 initialize the objects assigned to the + * entities. + */ + template + void insert(It first, It last, Args &&...args) { + ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); + Type::insert(first, last, std::forward(args)...); + + for(auto it = construction.empty() ? last : first; it != last; ++it) { + construction.publish(*owner, *it); + } + } + + /** + * @brief Forwards variables to mixins, if any. + * @param value A variable wrapped in an opaque container. + */ + void bind(any value) ENTT_NOEXCEPT final { + auto *reg = any_cast>(&value); + owner = reg ? reg : owner; + Type::bind(std::move(value)); + } + +private: + sigh &, const entity_type)> construction{}; + sigh &, const entity_type)> destruction{}; + sigh &, const entity_type)> update{}; + basic_registry *owner{}; +}; + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/entity/snapshot.hpp b/modules/entt/src/entt/entity/snapshot.hpp index 091f995..4bcd235 100644 --- a/modules/entt/src/entt/entity/snapshot.hpp +++ b/modules/entt/src/entt/entity/snapshot.hpp @@ -1,21 +1,23 @@ #ifndef ENTT_ENTITY_SNAPSHOT_HPP #define ENTT_ENTITY_SNAPSHOT_HPP - #include #include -#include #include +#include #include -#include +#include +#include #include "../config/config.h" +#include "../container/dense_map.hpp" +#include "../core/type_traits.hpp" +#include "component.hpp" #include "entity.hpp" #include "fwd.hpp" - +#include "registry.hpp" namespace entt { - /** * @brief Utility class to create snapshots from a registry. * @@ -28,94 +30,71 @@ namespace entt { */ template class basic_snapshot { - /*! @brief A registry is allowed to create snapshots. */ - friend class basic_registry; - - using follow_fn_type = Entity(const basic_registry &, const Entity); - - basic_snapshot(const basic_registry *source, Entity init, follow_fn_type *fn) ENTT_NOEXCEPT - : reg{source}, - seed{init}, - follow{fn} - {} + using entity_traits = entt_traits; template void get(Archive &archive, std::size_t sz, It first, It last) const { - archive(static_cast(sz)); + const auto view = reg->template view>(); + archive(typename entity_traits::entity_type(sz)); while(first != last) { const auto entt = *(first++); - if(reg->template has(entt)) { - if constexpr(std::is_empty_v) { - archive(entt); - } else { - archive(entt, reg->template get(entt)); - } + if(reg->template all_of(entt)) { + std::apply(archive, std::tuple_cat(std::make_tuple(entt), view.get(entt))); } } } - template - void component(Archive &archive, It first, It last, std::index_sequence) const { - std::array size{}; + template + void component(Archive &archive, It first, It last, std::index_sequence) const { + std::array size{}; auto begin = first; while(begin != last) { const auto entt = *(begin++); - ((reg->template has(entt) ? ++size[Indexes] : size[Indexes]), ...); + ((reg->template all_of(entt) ? ++size[Index] : 0u), ...); } - (get(archive, size[Indexes], first, last), ...); + (get(archive, size[Index], first, last), ...); } public: + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + + /** + * @brief Constructs an instance that is bound to a given registry. + * @param source A valid reference to a registry. + */ + basic_snapshot(const basic_registry &source) ENTT_NOEXCEPT + : reg{&source} {} + /*! @brief Default move constructor. */ - basic_snapshot(basic_snapshot &&) = default; + basic_snapshot(basic_snapshot &&) ENTT_NOEXCEPT = default; /*! @brief Default move assignment operator. @return This snapshot. */ - basic_snapshot & operator=(basic_snapshot &&) = default; + basic_snapshot &operator=(basic_snapshot &&) ENTT_NOEXCEPT = default; /** - * @brief Puts aside all the entities that are still in use. + * @brief Puts aside all the entities from the underlying registry. * * Entities are serialized along with their versions. Destroyed entities are - * not taken in consideration by this function. - * - * @tparam Archive Type of output archive. - * @param archive A valid reference to an output archive. - * @return An object of this type to continue creating the snapshot. - */ - template - const basic_snapshot & entities(Archive &archive) const { - archive(static_cast(reg->alive())); - reg->each([&archive](const auto entt) { archive(entt); }); - return *this; - } - - /** - * @brief Puts aside destroyed entities. - * - * Entities are serialized along with their versions. Entities that are - * still in use are not taken in consideration by this function. + * taken in consideration as well by this function. * * @tparam Archive Type of output archive. * @param archive A valid reference to an output archive. * @return An object of this type to continue creating the snapshot. */ template - const basic_snapshot & destroyed(Archive &archive) const { - auto size = reg->size() - reg->alive(); - archive(static_cast(size)); + const basic_snapshot &entities(Archive &archive) const { + const auto sz = reg->size(); - if(size) { - auto curr = seed; - archive(curr); + archive(typename entity_traits::entity_type(sz + 1u)); + archive(reg->released()); - for(--size; size; --size) { - curr = follow(*reg, curr); - archive(curr); - } + for(auto first = reg->data(), last = first + sz; first != last; ++first) { + archive(*first); } return *this; @@ -133,27 +112,15 @@ public: * @return An object of this type to continue creating the snapshot. */ template - const basic_snapshot & component(Archive &archive) const { - if constexpr(sizeof...(Component) == 1) { - const auto sz = reg->template size(); - const auto *entities = reg->template data(); - - archive(static_cast(sz)); - - for(std::remove_const_t pos{}; pos < sz; ++pos) { - const auto entt = entities[pos]; - - if constexpr(std::is_empty_v) { - archive(entt); - } else { - archive(entt, reg->template get(entt)); - } - }; + const basic_snapshot &component(Archive &archive) const { + if constexpr(sizeof...(Component) == 1u) { + const auto view = reg->template view(); + (component(archive, view.rbegin(), view.rend()), ...); + return *this; } else { (component(archive), ...); + return *this; } - - return *this; } /** @@ -171,18 +138,15 @@ public: * @return An object of this type to continue creating the snapshot. */ template - const basic_snapshot & component(Archive &archive, It first, It last) const { - component(archive, first, last, std::make_index_sequence{}); + const basic_snapshot &component(Archive &archive, It first, It last) const { + component(archive, first, last, std::index_sequence_for{}); return *this; } private: - const basic_registry *reg; - const Entity seed; - follow_fn_type *follow; + const basic_registry *reg; }; - /** * @brief Utility class to restore a snapshot as a whole. * @@ -195,59 +159,53 @@ private: */ template class basic_snapshot_loader { - /*! @brief A registry is allowed to create snapshot loaders. */ - friend class basic_registry; - - using force_fn_type = void(basic_registry &, const Entity, const bool); - - basic_snapshot_loader(basic_registry *source, force_fn_type *fn) ENTT_NOEXCEPT - : reg{source}, - force{fn} - { - // to restore a snapshot as a whole requires a clean registry - ENTT_ASSERT(reg->empty()); - } - - template - void assure(Archive &archive, bool destroyed) const { - Entity length{}; - archive(length); + using entity_traits = entt_traits; - while(length--) { - Entity entt{}; - archive(entt); - force(*reg, entt, destroyed); - } - } + template + void assign(Archive &archive) const { + typename entity_traits::entity_type length{}; + entity_type entt; - template - void assign(Archive &archive, Args... args) const { - Entity length{}; archive(length); - while(length--) { - static constexpr auto destroyed = false; - Entity entt{}; - - if constexpr(std::is_empty_v) { + if constexpr(ignore_as_empty_v) { + while(length--) { archive(entt); - force(*reg, entt, destroyed); - reg->template assign(args..., entt); - } else { - Type instance{}; + const auto entity = reg->valid(entt) ? entt : reg->create(entt); + ENTT_ASSERT(entity == entt, "Entity not available for use"); + reg->template emplace(entt); + } + } else { + Type instance; + + while(length--) { archive(entt, instance); - force(*reg, entt, destroyed); - reg->template assign(args..., entt, std::as_const(instance)); + const auto entity = reg->valid(entt) ? entt : reg->create(entt); + ENTT_ASSERT(entity == entt, "Entity not available for use"); + reg->template emplace(entt, std::move(instance)); } } } public: + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + + /** + * @brief Constructs an instance that is bound to a given registry. + * @param source A valid reference to a registry. + */ + basic_snapshot_loader(basic_registry &source) ENTT_NOEXCEPT + : reg{&source} { + // restoring a snapshot as a whole requires a clean registry + ENTT_ASSERT(reg->empty(), "Registry must be empty"); + } + /*! @brief Default move constructor. */ - basic_snapshot_loader(basic_snapshot_loader &&) = default; + basic_snapshot_loader(basic_snapshot_loader &&) ENTT_NOEXCEPT = default; /*! @brief Default move assignment operator. @return This loader. */ - basic_snapshot_loader & operator=(basic_snapshot_loader &&) = default; + basic_snapshot_loader &operator=(basic_snapshot_loader &&) ENTT_NOEXCEPT = default; /** * @brief Restores entities that were in use during serialization. @@ -260,26 +218,18 @@ public: * @return A valid loader to continue restoring data. */ template - const basic_snapshot_loader & entities(Archive &archive) const { - static constexpr auto destroyed = false; - assure(archive, destroyed); - return *this; - } + const basic_snapshot_loader &entities(Archive &archive) const { + typename entity_traits::entity_type length{}; + + archive(length); + std::vector all(length); + + for(std::size_t pos{}; pos < length; ++pos) { + archive(all[pos]); + } + + reg->assign(++all.cbegin(), all.cend(), all[0u]); - /** - * @brief Restores entities that were destroyed during serialization. - * - * This function restores the entities that were destroyed during - * serialization and gives them the versions they originally had. - * - * @tparam Archive Type of input archive. - * @param archive A valid reference to an input archive. - * @return A valid loader to continue restoring data. - */ - template - const basic_snapshot_loader & destroyed(Archive &archive) const { - static constexpr auto destroyed = true; - assure(archive, destroyed); return *this; } @@ -297,7 +247,7 @@ public: * @return A valid loader to continue restoring data. */ template - const basic_snapshot_loader & component(Archive &archive) const { + const basic_snapshot_loader &component(Archive &archive) const { (assign(archive), ...); return *this; } @@ -312,25 +262,25 @@ public: * * @return A valid loader to continue restoring data. */ - const basic_snapshot_loader & orphans() const { - reg->orphans([this](const auto entt) { - reg->destroy(entt); + const basic_snapshot_loader &orphans() const { + reg->each([this](const auto entt) { + if(reg->orphan(entt)) { + reg->release(entt); + } }); return *this; } private: - basic_registry *reg; - force_fn_type *force; + basic_registry *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 accomodate in a registry + * (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. @@ -344,12 +294,10 @@ private: */ template class basic_continuous_loader { - using traits_type = entt_traits; + using entity_traits = entt_traits; void destroy(Entity entt) { - const auto it = remloc.find(entt); - - if(it == remloc.cend()) { + if(const auto it = remloc.find(entt); it == remloc.cend()) { const auto local = reg->create(); remloc.emplace(entt, std::make_pair(local, true)); reg->destroy(local); @@ -363,67 +311,92 @@ class basic_continuous_loader { const auto local = reg->create(); remloc.emplace(entt, std::make_pair(local, true)); } else { - remloc[entt].first = reg->valid(remloc[entt].first) ? remloc[entt].first : reg->create(); + if(!reg->valid(remloc[entt].first)) { + remloc[entt].first = reg->create(); + } + // set the dirty flag remloc[entt].second = true; } } + template + auto update(int, Container &container) -> decltype(typename Container::mapped_type{}, void()) { + // map like container + Container other; + + for(auto &&pair: container) { + using first_type = std::remove_const_t::first_type>; + using second_type = typename std::decay_t::second_type; + + if constexpr(std::is_same_v && std::is_same_v) { + other.emplace(map(pair.first), map(pair.second)); + } else if constexpr(std::is_same_v) { + other.emplace(map(pair.first), std::move(pair.second)); + } else { + static_assert(std::is_same_v, "Neither the key nor the value are of entity type"); + other.emplace(std::move(pair.first), map(pair.second)); + } + } + + using std::swap; + swap(container, other); + } + + template + auto update(char, Container &container) -> decltype(typename Container::value_type{}, void()) { + // vector like container + static_assert(std::is_same_v, "Invalid value type"); + + for(auto &&entt: container) { + entt = map(entt); + } + } + template - void update(Other &instance, Member Type:: *member) { + void update([[maybe_unused]] Other &instance, [[maybe_unused]] Member Type::*member) { if constexpr(!std::is_same_v) { return; - } else if constexpr(std::is_same_v) { + } else if constexpr(std::is_same_v) { instance.*member = map(instance.*member); } else { // maybe a container? let's try... - for(auto &entt: instance.*member) { - entt = map(entt); - } - } - } - - template - void assure(Archive &archive, void(basic_continuous_loader:: *member)(Entity)) { - Entity length{}; - archive(length); - - while(length--) { - Entity entt{}; - archive(entt); - (this->*member)(entt); + update(0, instance.*member); } } template - void reset() { + void remove_if_exists() { for(auto &&ref: remloc) { const auto local = ref.second.first; if(reg->valid(local)) { - reg->template reset(local); + reg->template remove(local); } } } template - void assign(Archive &archive, [[maybe_unused]] Member Type:: *... member) { - Entity length{}; - archive(length); + void assign(Archive &archive, [[maybe_unused]] Member Type::*...member) { + typename entity_traits::entity_type length{}; + entity_type entt; - while(length--) { - Entity entt{}; + archive(length); - if constexpr(std::is_empty_v) { + if constexpr(ignore_as_empty_v) { + while(length--) { archive(entt); restore(entt); - reg->template assign_or_replace(map(entt)); - } else { - Other instance{}; + reg->template emplace_or_replace(map(entt)); + } + } else { + Other instance; + + while(length--) { archive(entt, instance); (update(instance, member), ...); restore(entt); - reg->template assign_or_replace(map(entt), std::as_const(instance)); + reg->template emplace_or_replace(map(entt), std::move(instance)); } } } @@ -433,18 +406,17 @@ public: using entity_type = Entity; /** - * @brief Constructs a loader that is bound to a given registry. + * @brief Constructs an instance that is bound to a given registry. * @param source A valid reference to a registry. */ basic_continuous_loader(basic_registry &source) ENTT_NOEXCEPT - : reg{&source} - {} + : reg{&source} {} /*! @brief Default move constructor. */ basic_continuous_loader(basic_continuous_loader &&) = default; /*! @brief Default move assignment operator. @return This loader. */ - basic_continuous_loader & operator=(basic_continuous_loader &&) = default; + basic_continuous_loader &operator=(basic_continuous_loader &&) = default; /** * @brief Restores entities that were in use during serialization. @@ -457,24 +429,24 @@ public: * @return A non-const reference to this loader. */ template - basic_continuous_loader & entities(Archive &archive) { - assure(archive, &basic_continuous_loader::restore); - return *this; - } + basic_continuous_loader &entities(Archive &archive) { + typename entity_traits::entity_type length{}; + entity_type entt{}; + + archive(length); + // discards the head of the list of destroyed entities + archive(entt); + + for(std::size_t pos{}, last = length - 1u; pos < last; ++pos) { + archive(entt); + + if(const auto entity = entity_traits::to_entity(entt); entity == pos) { + restore(entt); + } else { + destroy(entt); + } + } - /** - * @brief Restores entities that were destroyed during serialization. - * - * This function restores the entities that were destroyed during - * serialization and creates local counterparts for them if required. - * - * @tparam Archive Type of input archive. - * @param archive A valid reference to an input archive. - * @return A non-const reference to this loader. - */ - template - basic_continuous_loader & destroyed(Archive &archive) { - assure(archive, &basic_continuous_loader::destroy); return *this; } @@ -498,8 +470,8 @@ public: * @return A non-const reference to this loader. */ template - basic_continuous_loader & component(Archive &archive, Member Type:: *... member) { - (reset(), ...); + basic_continuous_loader &component(Archive &archive, Member Type::*...member) { + (remove_if_exists(), ...); (assign(archive, member...), ...); return *this; } @@ -512,7 +484,7 @@ public: * * @return A non-const reference to this loader. */ - basic_continuous_loader & shrink() { + basic_continuous_loader &shrink() { auto it = remloc.begin(); while(it != remloc.cend()) { @@ -544,9 +516,11 @@ public: * * @return A non-const reference to this loader. */ - basic_continuous_loader & orphans() { - reg->orphans([this](const auto entt) { - reg->destroy(entt); + basic_continuous_loader &orphans() { + reg->each([this](const auto entt) { + if(reg->orphan(entt)) { + reg->release(entt); + } }); return *this; @@ -554,19 +528,19 @@ public: /** * @brief Tests if a loader knows about a given entity. - * @param entt An entity identifier. + * @param entt A valid identifier. * @return True if `entity` is managed by the loader, false otherwise. */ - bool has(entity_type entt) const ENTT_NOEXCEPT { + [[nodiscard]] bool contains(entity_type entt) const ENTT_NOEXCEPT { return (remloc.find(entt) != remloc.cend()); } /** * @brief Returns the identifier to which an entity refers. - * @param entt An entity identifier. + * @param entt A valid identifier. * @return The local identifier if any, the null entity otherwise. */ - entity_type map(entity_type entt) const ENTT_NOEXCEPT { + [[nodiscard]] entity_type map(entity_type entt) const ENTT_NOEXCEPT { const auto it = remloc.find(entt); entity_type other = null; @@ -578,12 +552,10 @@ public: } private: - std::unordered_map> remloc; - basic_registry *reg; + dense_map> remloc; + basic_registry *reg; }; +} // namespace entt -} - - -#endif // ENTT_ENTITY_SNAPSHOT_HPP +#endif diff --git a/modules/entt/src/entt/entity/sparse_set.hpp b/modules/entt/src/entt/entity/sparse_set.hpp index 91fb755..3417f36 100644 --- a/modules/entt/src/entt/entity/sparse_set.hpp +++ b/modules/entt/src/entt/entity/sparse_set.hpp @@ -1,19 +1,151 @@ #ifndef ENTT_ENTITY_SPARSE_SET_HPP #define ENTT_ENTITY_SPARSE_SET_HPP - -#include +#include #include +#include +#include #include #include -#include -#include #include "../config/config.h" +#include "../core/algorithm.hpp" +#include "../core/any.hpp" +#include "../core/memory.hpp" +#include "../core/type_info.hpp" #include "entity.hpp" - +#include "fwd.hpp" namespace entt { +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct sparse_set_iterator final { + using value_type = typename Container::value_type; + using pointer = typename Container::const_pointer; + using reference = typename Container::const_reference; + using difference_type = typename Container::difference_type; + using iterator_category = std::random_access_iterator_tag; + + sparse_set_iterator() ENTT_NOEXCEPT + : packed{}, + offset{} {} + + sparse_set_iterator(const Container &ref, const difference_type idx) ENTT_NOEXCEPT + : packed{std::addressof(ref)}, + offset{idx} {} + + sparse_set_iterator &operator++() ENTT_NOEXCEPT { + return --offset, *this; + } + + sparse_set_iterator operator++(int) ENTT_NOEXCEPT { + sparse_set_iterator orig = *this; + return ++(*this), orig; + } + + sparse_set_iterator &operator--() ENTT_NOEXCEPT { + return ++offset, *this; + } + + sparse_set_iterator operator--(int) ENTT_NOEXCEPT { + sparse_set_iterator orig = *this; + return operator--(), orig; + } + + sparse_set_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + offset -= value; + return *this; + } + + sparse_set_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + sparse_set_iterator copy = *this; + return (copy += value); + } + + sparse_set_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); + } + + sparse_set_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); + } + + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + return packed->data()[index() - value]; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return packed->data() + index(); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return *operator->(); + } + + [[nodiscard]] difference_type index() const ENTT_NOEXCEPT { + return offset - 1; + } + +private: + const Container *packed; + difference_type offset; +}; + +template +[[nodiscard]] std::ptrdiff_t operator-(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return rhs.index() - lhs.index(); +} + +template +[[nodiscard]] bool operator==(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() == rhs.index(); +} + +template +[[nodiscard]] bool operator!=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +[[nodiscard]] bool operator<(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() > rhs.index(); +} + +template +[[nodiscard]] bool operator>(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() < rhs.index(); +} + +template +[[nodiscard]] bool operator<=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +template +[[nodiscard]] bool operator>=(const sparse_set_iterator &lhs, const sparse_set_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @brief Sparse set deletion policy. */ +enum class deletion_policy : std::uint8_t { + /*! @brief Swap-and-pop deletion policy. */ + swap_and_pop = 0u, + /*! @brief In-place deletion policy. */ + in_place = 1u +}; /** * @brief Basic sparse set implementation. @@ -31,194 +163,256 @@ namespace entt { * purpose of the framework. * * @note - * 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. - * - * @note - * Internal data structures arrange elements to maximize performance. Because of - * that, there are no guarantees that elements have the expected order when - * iterate directly the internal packed array (see `data` and `size` member - * functions for that). Use `begin` and `end` instead. + * 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 (see entt_traits for more details). + * @tparam Allocator Type of allocator used to manage memory and elements. */ -template -class sparse_set { - using traits_type = entt_traits; - - static_assert(ENTT_PAGE_SIZE && ((ENTT_PAGE_SIZE & (ENTT_PAGE_SIZE - 1)) == 0)); - static constexpr auto entt_per_page = ENTT_PAGE_SIZE / sizeof(typename entt_traits::entity_type); - - class iterator { - friend class sparse_set; - - using direct_type = const std::vector; - using index_type = typename traits_type::difference_type; - - iterator(direct_type *ref, const index_type idx) ENTT_NOEXCEPT - : direct{ref}, index{idx} - {} - - public: - using difference_type = index_type; - using value_type = Entity; - using pointer = const value_type *; - using reference = const value_type &; - using iterator_category = std::random_access_iterator_tag; - - iterator() ENTT_NOEXCEPT = default; - - iterator & operator++() ENTT_NOEXCEPT { - return --index, *this; - } - - iterator operator++(int) ENTT_NOEXCEPT { - iterator orig = *this; - return ++(*this), orig; - } - - iterator & operator--() ENTT_NOEXCEPT { - return ++index, *this; - } - - iterator operator--(int) ENTT_NOEXCEPT { - iterator orig = *this; - return --(*this), orig; - } - - iterator & operator+=(const difference_type value) ENTT_NOEXCEPT { - index -= value; - return *this; - } - - iterator operator+(const difference_type value) const ENTT_NOEXCEPT { - return iterator{direct, index-value}; - } - - inline iterator & operator-=(const difference_type value) ENTT_NOEXCEPT { - return (*this += -value); - } - - inline iterator operator-(const difference_type value) const ENTT_NOEXCEPT { - return (*this + -value); - } - - difference_type operator-(const iterator &other) const ENTT_NOEXCEPT { - return other.index - index; - } +template +class basic_sparse_set { + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using sparse_container_type = std::vector>; + using packed_container_type = std::vector; + using entity_traits = entt_traits; + + [[nodiscard]] auto sparse_ptr(const Entity entt) const { + const auto pos = static_cast(entity_traits::to_entity(entt)); + const auto page = pos / entity_traits::page_size; + return (page < sparse.size() && sparse[page]) ? (sparse[page] + fast_mod(pos, entity_traits::page_size)) : nullptr; + } - reference operator[](const difference_type value) const ENTT_NOEXCEPT { - const auto pos = size_type(index-value-1); - return (*direct)[pos]; - } + [[nodiscard]] auto &sparse_ref(const Entity entt) const { + ENTT_ASSERT(sparse_ptr(entt), "Invalid element"); + const auto pos = static_cast(entity_traits::to_entity(entt)); + return sparse[pos / entity_traits::page_size][fast_mod(pos, entity_traits::page_size)]; + } - bool operator==(const iterator &other) const ENTT_NOEXCEPT { - return other.index == index; - } + [[nodiscard]] auto &assure_at_least(const Entity entt) { + const auto pos = static_cast(entity_traits::to_entity(entt)); + const auto page = pos / entity_traits::page_size; - inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this == other); + if(!(page < sparse.size())) { + sparse.resize(page + 1u, nullptr); } - bool operator<(const iterator &other) const ENTT_NOEXCEPT { - return index > other.index; + if(!sparse[page]) { + auto page_allocator{packed.get_allocator()}; + sparse[page] = alloc_traits::allocate(page_allocator, entity_traits::page_size); + std::uninitialized_fill(sparse[page], sparse[page] + entity_traits::page_size, null); } - bool operator>(const iterator &other) const ENTT_NOEXCEPT { - return index < other.index; - } + auto &elem = sparse[page][fast_mod(pos, entity_traits::page_size)]; + ENTT_ASSERT(entity_traits::to_version(elem) == entity_traits::to_version(tombstone), "Slot not available"); + return elem; + } - inline bool operator<=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this > other); - } + void release_sparse_pages() { + auto page_allocator{packed.get_allocator()}; - inline bool operator>=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this < other); + for(auto &&page: sparse) { + if(page != nullptr) { + std::destroy(page, page + entity_traits::page_size); + alloc_traits::deallocate(page_allocator, page, entity_traits::page_size); + page = nullptr; + } } + } - pointer operator->() const ENTT_NOEXCEPT { - const auto pos = size_type(index-1); - return &(*direct)[pos]; - } +private: + virtual const void *get_at(const std::size_t) const { + return nullptr; + } - inline reference operator*() const ENTT_NOEXCEPT { - return *operator->(); - } + virtual void swap_at(const std::size_t, const std::size_t) {} + virtual void move_element(const std::size_t, const std::size_t) {} - private: - direct_type *direct; - index_type index; - }; +protected: + /*! @brief Random access iterator type. */ + using basic_iterator = internal::sparse_set_iterator; - void assure(const std::size_t page) { - if(!(page < reverse.size())) { - reverse.resize(page+1); + /** + * @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 swap_and_pop(basic_iterator first, basic_iterator last) { + for(; first != last; ++first) { + auto &self = sparse_ref(*first); + const auto entt = entity_traits::to_entity(self); + sparse_ref(packed.back()) = entity_traits::combine(entt, entity_traits::to_integral(packed.back())); + packed[static_cast(entt)] = packed.back(); + // unnecessary but it helps to detect nasty bugs + ENTT_ASSERT((packed.back() = tombstone, true), ""); + // lazy self-assignment guard + self = null; + packed.pop_back(); } + } - if(!reverse[page].first) { - reverse[page].first = std::make_unique(entt_per_page); - // null is safe in all cases for our purposes - std::fill_n(reverse[page].first.get(), entt_per_page, null); + /** + * @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 in_place_pop(basic_iterator first, basic_iterator last) { + for(; first != last; ++first) { + const auto entt = entity_traits::to_entity(std::exchange(sparse_ref(*first), null)); + packed[static_cast(entt)] = std::exchange(free_list, entity_traits::combine(entt, entity_traits::reserved)); } } - auto index(const Entity entt) const ENTT_NOEXCEPT { - const auto identifier = entt & traits_type::entity_mask; - const auto page = size_type(identifier / entt_per_page); - const auto offset = size_type(identifier & (entt_per_page - 1)); - return std::make_pair(page, offset); + /** + * @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(!contains(entt), "Set already contains entity"); + + if(auto &elem = assure_at_least(entt); free_list == null || force_back) { + packed.push_back(entt); + elem = entity_traits::combine(static_cast(packed.size() - 1u), entity_traits::to_integral(entt)); + return begin(); + } else { + const auto pos = static_cast(entity_traits::to_entity(free_list)); + elem = entity_traits::combine(entity_traits::to_integral(free_list), entity_traits::to_integral(entt)); + free_list = std::exchange(packed[pos], entt); + return --(end() - pos); + } } public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; /*! @brief Underlying entity identifier. */ using entity_type = Entity; + /*! @brief Underlying version type. */ + using version_type = typename entity_traits::version_type; /*! @brief Unsigned integer type. */ - using size_type = std::size_t; + using size_type = typename packed_container_type::size_type; + /*! @brief Pointer type to contained entities. */ + using pointer = typename packed_container_type::const_pointer; /*! @brief Random access iterator type. */ - using iterator_type = iterator; + using iterator = basic_iterator; + /*! @brief Constant random access iterator type. */ + using const_iterator = iterator; + /*! @brief Reverse iterator type. */ + using reverse_iterator = std::reverse_iterator; + /*! @brief Constant reverse iterator type. */ + using const_reverse_iterator = reverse_iterator; /*! @brief Default constructor. */ - sparse_set() = default; + basic_sparse_set() + : basic_sparse_set{type_id()} {} /** - * @brief Copy constructor. - * @param other The instance to copy from. + * @brief Constructs an empty container with a given allocator. + * @param allocator The allocator to use. */ - sparse_set(const sparse_set &other) - : reverse{}, - direct{other.direct} - { - for(size_type pos{}, last = other.reverse.size(); pos < last; ++pos) { - if(other.reverse[pos].first) { - assure(pos); - std::copy_n(other.reverse[pos].first.get(), entt_per_page, reverse[pos].first.get()); - reverse[pos].second = other.reverse[pos].second; - } - } - } + explicit basic_sparse_set(const allocator_type &allocator) + : basic_sparse_set{type_id(), 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 Default move constructor. */ - sparse_set(sparse_set &&) = default; + /** + * @brief Constructs an empty container with the given value type, policy + * and allocator. + * @param value 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 &value, deletion_policy pol = deletion_policy::swap_and_pop, const allocator_type &allocator = {}) + : sparse{allocator}, + packed{allocator}, + info{&value}, + free_list{tombstone}, + mode{pol} {} + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_sparse_set(basic_sparse_set &&other) ENTT_NOEXCEPT + : sparse{std::move(other.sparse)}, + packed{std::move(other.packed)}, + info{other.info}, + free_list{std::exchange(other.free_list, tombstone)}, + mode{other.mode} {} + + /** + * @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) ENTT_NOEXCEPT + : sparse{std::move(other.sparse), allocator}, + packed{std::move(other.packed), allocator}, + info{other.info}, + free_list{std::exchange(other.free_list, tombstone)}, + mode{other.mode} { + ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.get_allocator() == other.packed.get_allocator(), "Copying a sparse set is not allowed"); + } /*! @brief Default destructor. */ - virtual ~sparse_set() ENTT_NOEXCEPT = default; + virtual ~basic_sparse_set() { + release_sparse_pages(); + } /** - * @brief Copy assignment operator. - * @param other The instance to copy from. + * @brief Move assignment operator. + * @param other The instance to move from. * @return This sparse set. */ - sparse_set & operator=(const sparse_set &other) { - if(&other != this) { - auto tmp{other}; - *this = std::move(tmp); - } - + basic_sparse_set &operator=(basic_sparse_set &&other) ENTT_NOEXCEPT { + ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.get_allocator() == other.packed.get_allocator(), "Copying a sparse set is not allowed"); + + release_sparse_pages(); + sparse = std::move(other.sparse); + packed = std::move(other.packed); + info = other.info; + free_list = std::exchange(other.free_list, tombstone); + mode = other.mode; return *this; } - /*! @brief Default move assignment operator. @return This sparse set. */ - sparse_set & operator=(sparse_set &&) = default; + /** + * @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) { + using std::swap; + swap(sparse, other.sparse); + swap(packed, other.packed); + swap(info, other.info); + swap(free_list, other.free_list); + swap(mode, other.mode); + } + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_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 ENTT_NOEXCEPT { + return mode; + } /** * @brief Increases the capacity of a sparse set. @@ -229,7 +423,7 @@ public: * @param cap Desired capacity. */ virtual void reserve(const size_type cap) { - direct.reserve(cap); + packed.reserve(cap); } /** @@ -237,24 +431,13 @@ public: * allocated space for. * @return Capacity of the sparse set. */ - size_type capacity() const ENTT_NOEXCEPT { - return direct.capacity(); + [[nodiscard]] virtual size_type capacity() const ENTT_NOEXCEPT { + return packed.capacity(); } /*! @brief Requests the removal of unused capacity. */ virtual void shrink_to_fit() { - while(!reverse.empty() && !reverse.back().second) { - reverse.pop_back(); - } - - for(auto &&data: reverse) { - if(!data.second) { - data.first.reset(); - } - } - - reverse.shrink_to_fit(); - direct.shrink_to_fit(); + packed.shrink_to_fit(); } /** @@ -267,8 +450,8 @@ public: * * @return Extent of the sparse set. */ - size_type extent() const ENTT_NOEXCEPT { - return reverse.size() * entt_per_page; + [[nodiscard]] size_type extent() const ENTT_NOEXCEPT { + return sparse.size() * entity_traits::page_size; } /** @@ -281,35 +464,24 @@ public: * * @return Number of elements. */ - size_type size() const ENTT_NOEXCEPT { - return direct.size(); + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return packed.size(); } /** * @brief Checks whether a sparse set is empty. * @return True if the sparse set is empty, false otherwise. */ - bool empty() const ENTT_NOEXCEPT { - return direct.empty(); + [[nodiscard]] bool empty() const ENTT_NOEXCEPT { + return packed.empty(); } /** * @brief Direct access to the internal packed array. - * - * The returned pointer is such that range `[data(), data() + size()]` is - * always a valid range, even if the container is empty. - * - * @note - * There are no guarantees on the order, even though `respect` has been - * previously invoked. Internal data structures arrange elements to maximize - * performance. Accessing them directly gives a performance boost but less - * guarantees. Use `begin` and `end` if you want to iterate the sparse set - * in the expected order. - * * @return A pointer to the internal packed array. */ - const entity_type * data() const ENTT_NOEXCEPT { - return direct.data(); + [[nodiscard]] pointer data() const ENTT_NOEXCEPT { + return packed.data(); } /** @@ -319,52 +491,107 @@ public: * array. If the sparse set is empty, the returned iterator will be equal to * `end()`. * - * @note - * Input iterators stay true to the order imposed by a call to `respect`. - * - * @return An iterator to the first entity of the internal packed array. + * @return An iterator to the first entity of the sparse set. */ - iterator_type begin() const ENTT_NOEXCEPT { - const typename traits_type::difference_type pos = direct.size(); - return iterator_type{&direct, pos}; + [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT { + const auto pos = static_cast(packed.size()); + return iterator{packed, pos}; + } + + /*! @copydoc begin */ + [[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT { + return begin(); } /** * @brief Returns an iterator to the end. * * The returned iterator points to the element following the last entity in - * the internal packed array. Attempting to dereference the returned - * iterator results in undefined behavior. + * a sparse set. Attempting to dereference the returned iterator results in + * undefined behavior. * - * @note - * Input iterators stay true to the order imposed by a call to `respect`. + * @return An iterator to the element following the last entity of a sparse + * set. + */ + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return iterator{packed, {}}; + } + + /*! @copydoc end */ + [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { + return end(); + } + + /** + * @brief Returns a reverse iterator to the beginning. + * + * The returned iterator points to the first entity of the reversed internal + * packed array. 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]] const_reverse_iterator rbegin() const ENTT_NOEXCEPT { + return std::make_reverse_iterator(end()); + } + + /*! @copydoc rbegin */ + [[nodiscard]] const_reverse_iterator crbegin() const ENTT_NOEXCEPT { + return rbegin(); + } + + /** + * @brief Returns a reverse iterator to the end. + * + * The returned iterator points to the element following the last entity in + * the reversed sparse set. Attempting to dereference the returned iterator + * results in undefined behavior. * * @return An iterator to the element following the last entity of the - * internal packed array. + * reversed sparse set. */ - iterator_type end() const ENTT_NOEXCEPT { - return iterator_type{&direct, {}}; + [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT { + return std::make_reverse_iterator(begin()); + } + + /*! @copydoc rend */ + [[nodiscard]] const_reverse_iterator crend() const ENTT_NOEXCEPT { + return rend(); } /** * @brief Finds an entity. - * @param entt A valid entity identifier. + * @param entt A valid identifier. * @return An iterator to the given entity if it's found, past the end * iterator otherwise. */ - iterator_type find(const entity_type entt) const ENTT_NOEXCEPT { - return has(entt) ? --(end() - get(entt)) : end(); + [[nodiscard]] iterator find(const entity_type entt) const ENTT_NOEXCEPT { + return contains(entt) ? --(end() - index(entt)) : end(); } /** * @brief Checks if a sparse set contains an entity. - * @param entt A valid entity identifier. + * @param entt A valid identifier. * @return True if the sparse set contains the entity, false otherwise. */ - bool has(const entity_type entt) const ENTT_NOEXCEPT { - auto [page, offset] = index(entt); - // testing against null permits to avoid accessing the direct vector - return (page < reverse.size() && reverse[page].second && reverse[page].first[offset] != null); + [[nodiscard]] bool contains(const entity_type entt) const ENTT_NOEXCEPT { + const auto elem = sparse_ptr(entt); + constexpr auto cap = entity_traits::to_entity(null); + // testing versions permits to avoid accessing the packed array + return elem && (((~cap & entity_traits::to_integral(entt)) ^ entity_traits::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 ENTT_NOEXCEPT { + const auto elem = sparse_ptr(entt); + constexpr auto fallback = entity_traits::to_version(tombstone); + return elem ? entity_traits::to_version(*elem) : fallback; } /** @@ -372,17 +599,52 @@ public: * * @warning * Attempting to get the position of an entity that doesn't belong to the - * sparse set results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * sparse set doesn't contain the given entity. + * sparse set results in undefined behavior. * - * @param entt A valid entity identifier. + * @param entt A valid identifier. * @return The position of the entity in the sparse set. */ - size_type get(const entity_type entt) const ENTT_NOEXCEPT { - ENTT_ASSERT(has(entt)); - auto [page, offset] = index(entt); - return size_type(reverse[page].first[offset]); + [[nodiscard]] size_type index(const entity_type entt) const ENTT_NOEXCEPT { + ENTT_ASSERT(contains(entt), "Set does not contain entity"); + return static_cast(entity_traits::to_entity(sparse_ref(entt))); + } + + /** + * @brief Returns the entity at specified location, with bounds checking. + * @param pos The position for which to return the entity. + * @return The entity at specified location if any, a null entity otherwise. + */ + [[nodiscard]] entity_type at(const size_type pos) const ENTT_NOEXCEPT { + return pos < packed.size() ? packed[pos] : null; + } + + /** + * @brief Returns the entity at specified location, without bounds checking. + * @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 ENTT_NOEXCEPT { + ENTT_ASSERT(pos < packed.size(), "Position is 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. + */ + const void *get(const entity_type entt) const ENTT_NOEXCEPT { + return get_at(index(entt)); + } + + /*! @copydoc get */ + void *get(const entity_type entt) ENTT_NOEXCEPT { + return const_cast(std::as_const(*this).get(entt)); } /** @@ -390,19 +652,30 @@ public: * * @warning * Attempting to assign an entity that already belongs to the sparse set - * results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * sparse set already contains the given entity. + * results in undefined behavior. * - * @param entt A valid entity identifier. + * @param entt A valid identifier. + * @param value Optional opaque value to forward to mixins, if any. + * @return Iterator pointing to the emplaced element in case of success, the + * `end()` iterator otherwise. */ - void construct(const entity_type entt) { - ENTT_ASSERT(!has(entt)); - auto [page, offset] = index(entt); - assure(page); - reverse[page].first[offset] = entity_type(direct.size()); - reverse[page].second++; - direct.push_back(entt); + iterator emplace(const entity_type entt, const void *value = nullptr) { + return try_emplace(entt, false, value); + } + + /** + * @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. + */ + void bump(const entity_type entt) { + auto &entity = sparse_ref(entt); + entity = entity_traits::combine(entity_traits::to_integral(entity), entity_traits::to_integral(entt)); + packed[static_cast(entity_traits::to_entity(entity))] = entt; } /** @@ -410,71 +683,208 @@ public: * * @warning * Attempting to assign an entity that already belongs to the sparse set - * results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * sparse set already contains the given entity. + * results in undefined behavior. * - * @tparam It Type of forward iterator. + * @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 Iterator pointing to the first element inserted in case of + * success, the `end()` iterator otherwise. */ template - void batch(It first, It last) { - std::for_each(first, last, [next = entity_type(direct.size()), this](const auto entt) mutable { - ENTT_ASSERT(!has(entt)); - auto [page, offset] = index(entt); - assure(page); - reverse[page].first[offset] = next++; - reverse[page].second++; - }); + iterator insert(It first, It last) { + for(auto it = first; it != last; ++it) { + try_emplace(*it, true); + } - direct.insert(direct.end(), first, last); + return first == last ? end() : find(*first); } /** - * @brief Removes an entity from a sparse set. + * @brief Erases an entity from a sparse set. * * @warning - * Attempting to remove an entity that doesn't belong to the sparse set - * results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * sparse set doesn't contain the given entity. + * Attempting to erase an entity that doesn't belong to the sparse set + * results in undefined behavior. * - * @param entt A valid entity identifier. + * @param entt A valid identifier. */ - virtual void destroy(const entity_type entt) { - ENTT_ASSERT(has(entt)); - auto [from_page, from_offset] = index(entt); - auto [to_page, to_offset] = index(direct.back()); - direct[size_type(reverse[from_page].first[from_offset])] = direct.back(); - reverse[to_page].first[to_offset] = reverse[from_page].first[from_offset]; - reverse[from_page].first[from_offset] = null; - reverse[from_page].second--; - direct.pop_back(); + void erase(const entity_type entt) { + const auto it = --(end() - index(entt)); + (mode == deletion_policy::in_place) ? in_place_pop(it, it + 1u) : swap_and_pop(it, it + 1u); } /** - * @brief Swaps two entities in the internal packed array. + * @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(std::is_same_v) { + (mode == deletion_policy::in_place) ? in_place_pop(first, last) : swap_and_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{}; + + for(; first != last; ++first) { + count += remove(*first); + } + + return count; + } + + /*! @brief Removes all tombstones from the packed array of a sparse set. */ + void compact() { + size_type from = packed.size(); + for(; from && packed[from - 1u] == tombstone; --from) {} + + for(auto *it = &free_list; *it != null && from; it = std::addressof(packed[entity_traits::to_entity(*it)])) { + if(const size_type to = entity_traits::to_entity(*it); to < from) { + --from; + move_element(from, to); + + using std::swap; + swap(packed[from], packed[to]); + + const auto entity = static_cast(to); + sparse_ref(packed[to]) = entity_traits::combine(entity, entity_traits::to_integral(packed[to])); + *it = entity_traits::combine(static_cast(from), entity_traits::reserved); + for(; from && packed[from - 1u] == tombstone; --from) {} + } + } + + free_list = tombstone; + packed.resize(from); + } + + /** + * @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.
- * An assertion will abort the execution at runtime in debug mode if the - * sparse set doesn't contain the given entities. + * in undefined behavior. * - * @param lhs A valid position within the sparse set. - * @param rhs A valid position within the sparse set. + * @param lhs A valid identifier. + * @param rhs A valid identifier. */ - void swap(const size_type lhs, const size_type rhs) ENTT_NOEXCEPT { - ENTT_ASSERT(lhs < direct.size()); - ENTT_ASSERT(rhs < direct.size()); - auto [src_page, src_offset] = index(direct[lhs]); - auto [dst_page, dst_offset] = index(direct[rhs]); - std::swap(reverse[src_page].first[src_offset], reverse[dst_page].first[dst_offset]); - std::swap(direct[lhs], direct[rhs]); + void swap_elements(const entity_type lhs, const entity_type rhs) { + ENTT_ASSERT(contains(lhs) && contains(rhs), "Set does not contain entities"); + + auto &entt = sparse_ref(lhs); + auto &other = sparse_ref(rhs); + + const auto from = entity_traits::to_entity(entt); + const auto to = entity_traits::to_entity(other); + + // basic no-leak guarantee (with invalid state) if swapping throws + swap_at(static_cast(from), static_cast(to)); + entt = entity_traits::combine(to, entity_traits::to_integral(packed[from])); + other = entity_traits::combine(from, entity_traits::to_integral(packed[to])); + + using std::swap; + swap(packed[from], packed[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(!(length > packed.size()), "Length exceeds the number of elements"); + ENTT_ASSERT(free_list == null, "Partial sorting with tombstones is not supported"); + + algo(packed.rend() - length, packed.rend(), std::move(compare), std::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_at(next, idx); + const auto entity = static_cast(curr); + sparse_ref(entt) = entity_traits::combine(entity, entity_traits::to_integral(packed[curr])); + curr = std::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) { + compact(); + sort_n(packed.size(), std::move(compare), std::move(algo), std::forward(args)...); } /** @@ -482,7 +892,7 @@ public: * * Entities that are part of both the sparse sets are ordered internally * according to the order they have in `other`. All the other entities goes - * to the end of the list and there are no guarantess on their order.
+ * to the end of the list and there are no guarantees on their order.
* In other terms, this function can be used to impose the same order on two * sets by using one of them as a master and the other one as a slave. * @@ -490,46 +900,62 @@ public: * the expected order after a call to `respect`. See `begin` and `end` for * more details. * - * @note - * Attempting to iterate elements using the raw pointer returned by `data` - * gives no guarantees on the order, even though `respect` has been invoked. - * * @param other The sparse sets that imposes the order of the entities. */ - virtual void respect(const sparse_set &other) ENTT_NOEXCEPT { + void respect(const basic_sparse_set &other) { + compact(); + const auto to = other.end(); auto from = other.begin(); - size_type pos = direct.size() - 1; - - while(pos && from != to) { - if(has(*from)) { - if(*from != direct[pos]) { - swap(pos, get(*from)); + for(size_type pos = packed.size() - 1; pos && from != to; ++from) { + if(contains(*from)) { + if(*from != packed[pos]) { + // basic no-leak guarantee (with invalid state) if swapping throws + swap_elements(packed[pos], *from); } --pos; } + } + } - ++from; + /*! @brief Clears a sparse set. */ + void clear() { + if(const auto last = end(); free_list == null) { + in_place_pop(begin(), last); + } else { + for(auto &&entity: *this) { + // tombstone filter on itself + if(const auto it = find(entity); it != last) { + in_place_pop(it, it + 1u); + } + } } + + free_list = tombstone; + packed.clear(); } /** - * @brief Resets a sparse set. + * @brief Returned value type, if any. + * @return Returned value type, if any. */ - virtual void reset() { - reverse.clear(); - direct.clear(); + const type_info &type() const ENTT_NOEXCEPT { + return *info; } + /*! @brief Forwards variables to mixins, if any. */ + virtual void bind(any) ENTT_NOEXCEPT {} + private: - std::vector, size_type>> reverse; - std::vector direct; + sparse_container_type sparse; + packed_container_type packed; + const type_info *info; + entity_type free_list; + deletion_policy mode; }; +} // namespace entt -} - - -#endif // ENTT_ENTITY_SPARSE_SET_HPP +#endif diff --git a/modules/entt/src/entt/entity/storage.hpp b/modules/entt/src/entt/entity/storage.hpp index 911a731..3343b18 100644 --- a/modules/entt/src/entt/entity/storage.hpp +++ b/modules/entt/src/entt/entity/storage.hpp @@ -1,169 +1,482 @@ #ifndef ENTT_ENTITY_STORAGE_HPP #define ENTT_ENTITY_STORAGE_HPP - -#include +#include #include -#include +#include +#include +#include #include #include -#include -#include #include "../config/config.h" -#include "../core/algorithm.hpp" -#include "sparse_set.hpp" +#include "../core/compressed_pair.hpp" +#include "../core/iterator.hpp" +#include "../core/memory.hpp" +#include "../core/type_info.hpp" +#include "component.hpp" #include "entity.hpp" - +#include "fwd.hpp" +#include "sigh_storage_mixin.hpp" +#include "sparse_set.hpp" namespace entt { +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +class storage_iterator final { + friend storage_iterator; + + using container_type = std::remove_const_t; + using alloc_traits = std::allocator_traits; + using comp_traits = component_traits; + + using iterator_traits = std::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 = typename iterator_traits::value_type; + using pointer = typename iterator_traits::pointer; + using reference = typename iterator_traits::reference; + using difference_type = typename iterator_traits::difference_type; + using iterator_category = std::random_access_iterator_tag; + + storage_iterator() ENTT_NOEXCEPT = default; + + storage_iterator(Container *ref, difference_type idx) ENTT_NOEXCEPT + : packed{ref}, + offset{idx} {} + + template, typename = std::enable_if_t> + storage_iterator(const storage_iterator> &other) ENTT_NOEXCEPT + : packed{other.packed}, + offset{other.offset} {} + + storage_iterator &operator++() ENTT_NOEXCEPT { + return --offset, *this; + } + + storage_iterator operator++(int) ENTT_NOEXCEPT { + storage_iterator orig = *this; + return ++(*this), orig; + } + + storage_iterator &operator--() ENTT_NOEXCEPT { + return ++offset, *this; + } + + storage_iterator operator--(int) ENTT_NOEXCEPT { + storage_iterator orig = *this; + return operator--(), orig; + } + + storage_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + offset -= value; + return *this; + } + + storage_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + storage_iterator copy = *this; + return (copy += value); + } + + storage_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); + } + + storage_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); + } + + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + const auto pos = index() - value; + return (*packed)[pos / comp_traits::page_size][fast_mod(pos, comp_traits::page_size)]; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + const auto pos = index(); + return (*packed)[pos / comp_traits::page_size] + fast_mod(pos, comp_traits::page_size); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return *operator->(); + } + + [[nodiscard]] difference_type index() const ENTT_NOEXCEPT { + return offset - 1; + } + +private: + Container *packed; + difference_type offset; +}; + +template +[[nodiscard]] std::ptrdiff_t operator-(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return rhs.index() - lhs.index(); +} + +template +[[nodiscard]] bool operator==(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() == rhs.index(); +} + +template +[[nodiscard]] bool operator!=(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +[[nodiscard]] bool operator<(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() > rhs.index(); +} + +template +[[nodiscard]] bool operator>(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return lhs.index() < rhs.index(); +} + +template +[[nodiscard]] bool operator<=(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +template +[[nodiscard]] bool operator>=(const storage_iterator &lhs, const storage_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +template +class extended_storage_iterator final { + template + friend class extended_storage_iterator; + +public: + using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval()), std::forward_as_tuple(*std::declval()...))); + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + extended_storage_iterator() = default; + + extended_storage_iterator(It base, Other... other) + : it{base, other...} {} + + template && ...) && (std::is_constructible_v && ...)>> + extended_storage_iterator(const extended_storage_iterator &other) + : it{other.it} {} + + extended_storage_iterator &operator++() ENTT_NOEXCEPT { + return ++std::get(it), (++std::get(it), ...), *this; + } + + extended_storage_iterator operator++(int) ENTT_NOEXCEPT { + extended_storage_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return {*std::get(it), *std::get(it)...}; + } + + template + friend bool operator==(const extended_storage_iterator &, const extended_storage_iterator &) ENTT_NOEXCEPT; + +private: + std::tuple it; +}; + +template +[[nodiscard]] bool operator==(const extended_storage_iterator &lhs, const extended_storage_iterator &rhs) ENTT_NOEXCEPT { + return std::get<0>(lhs.it) == std::get<0>(rhs.it); +} + +template +[[nodiscard]] bool operator!=(const extended_storage_iterator &lhs, const extended_storage_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ /** * @brief Basic storage implementation. * - * This class is a refinement of a sparse set that associates an object to an - * entity. The main purpose of this class is to extend sparse sets to store - * components in a registry. It guarantees fast access both to the elements and - * to the entities. - * - * @note - * Entities and objects have the same order. It's guaranteed both in case of raw - * access (either to entities or objects) and when using input iterators. - * - * @note - * Internal data structures arrange elements to maximize performance. Because of - * that, there are no guarantees that elements have the expected order when - * iterate directly the internal packed array (see `raw` and `size` member - * functions for that). Use `begin` and `end` instead. + * 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. Temporary objects are returned in - * place of the instances of the components and raw access isn't available for - * them. - * - * @sa sparse_set + * 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 Entity A valid entity type (see entt_traits for more details). * @tparam Type Type of objects assigned to the entities. + * @tparam Allocator Type of allocator used to manage memory and elements. */ -template> -class basic_storage: public sparse_set { - using underlying_type = sparse_set; - using traits_type = entt_traits; +template +class basic_storage: public basic_sparse_set::template rebind_alloc> { + static_assert(std::is_move_constructible_v && std::is_move_assignable_v, "The type must be at least move constructible/assignable"); + + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using underlying_type = basic_sparse_set>; + using container_type = std::vector>; + using comp_traits = component_traits; + + [[nodiscard]] auto &element_at(const std::size_t pos) const { + return packed.first()[pos / comp_traits::page_size][fast_mod(pos, comp_traits::page_size)]; + } - template - class iterator { - friend class basic_storage; + auto assure_at_least(const std::size_t pos) { + auto &&container = packed.first(); + const auto idx = pos / comp_traits::page_size; - using instance_type = std::conditional_t, std::vector>; - using index_type = typename traits_type::difference_type; + if(!(idx < container.size())) { + auto curr = container.size(); + container.resize(idx + 1u, nullptr); - iterator(instance_type *ref, const index_type idx) ENTT_NOEXCEPT - : instances{ref}, index{idx} - {} + ENTT_TRY { + for(const auto last = container.size(); curr < last; ++curr) { + container[curr] = alloc_traits::allocate(packed.second(), comp_traits::page_size); + } + } + ENTT_CATCH { + container.resize(curr); + ENTT_THROW; + } + } - public: - using difference_type = index_type; - using value_type = Type; - using pointer = std::conditional_t; - using reference = std::conditional_t; - using iterator_category = std::random_access_iterator_tag; + return container[idx] + fast_mod(pos, comp_traits::page_size); + } - iterator() ENTT_NOEXCEPT = default; + 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 = assure_at_least(static_cast(it.index())); + entt::uninitialized_construct_using_allocator(to_address(elem), packed.second(), std::forward(args)...); + } + ENTT_CATCH { + if constexpr(comp_traits::in_place_delete) { + base_type::in_place_pop(it, it + 1u); + } else { + base_type::swap_and_pop(it, it + 1u); + } - iterator & operator++() ENTT_NOEXCEPT { - return --index, *this; + ENTT_THROW; } - iterator operator++(int) ENTT_NOEXCEPT { - iterator orig = *this; - return ++(*this), orig; - } + return it; + } - iterator & operator--() ENTT_NOEXCEPT { - return ++index, *this; + void shrink_to_size(const std::size_t sz) { + for(auto pos = sz, length = base_type::size(); pos < length; ++pos) { + if constexpr(comp_traits::in_place_delete) { + if(base_type::at(pos) != tombstone) { + std::destroy_at(std::addressof(element_at(pos))); + } + } else { + std::destroy_at(std::addressof(element_at(pos))); + } } - iterator operator--(int) ENTT_NOEXCEPT { - iterator orig = *this; - return --(*this), orig; - } + auto &&container = packed.first(); + auto page_allocator{packed.second()}; + const auto from = (sz + comp_traits::page_size - 1u) / comp_traits::page_size; - iterator & operator+=(const difference_type value) ENTT_NOEXCEPT { - index -= value; - return *this; + for(auto pos = from, last = container.size(); pos < last; ++pos) { + alloc_traits::deallocate(page_allocator, container[pos], comp_traits::page_size); } - iterator operator+(const difference_type value) const ENTT_NOEXCEPT { - return iterator{instances, index-value}; - } + container.resize(from); + } - inline iterator & operator-=(const difference_type value) ENTT_NOEXCEPT { - return (*this += -value); - } +private: + const void *get_at(const std::size_t pos) const final { + return std::addressof(element_at(pos)); + } - inline iterator operator-(const difference_type value) const ENTT_NOEXCEPT { - return (*this + -value); - } + void swap_at(const std::size_t lhs, const std::size_t rhs) final { + using std::swap; + swap(element_at(lhs), element_at(rhs)); + } - difference_type operator-(const iterator &other) const ENTT_NOEXCEPT { - return other.index - index; - } + void move_element(const std::size_t from, const std::size_t to) final { + auto &elem = element_at(from); + entt::uninitialized_construct_using_allocator(to_address(assure_at_least(to)), packed.second(), std::move(elem)); + std::destroy_at(std::addressof(elem)); + } - reference operator[](const difference_type value) const ENTT_NOEXCEPT { - const auto pos = size_type(index-value-1); - return (*instances)[pos]; +protected: + /** + * @brief Erases elements from a storage. + * @param first An iterator to the first element to erase. + * @param last An iterator past the last element to erase. + */ + void swap_and_pop(typename underlying_type::basic_iterator first, typename underlying_type::basic_iterator last) override { + for(; first != last; ++first) { + // cannot use first::index() because it would break with cross iterators + const auto pos = base_type::index(*first); + auto &elem = element_at(base_type::size() - 1u); + // destroying on exit allows reentrant destructors + [[maybe_unused]] auto unused = std::exchange(element_at(pos), std::move(elem)); + std::destroy_at(std::addressof(elem)); + base_type::swap_and_pop(first, first + 1u); } + } - bool operator==(const iterator &other) const ENTT_NOEXCEPT { - return other.index == index; + /** + * @brief Erases elements from a storage. + * @param first An iterator to the first element to erase. + * @param last An iterator past the last element to erase. + */ + void in_place_pop(typename underlying_type::basic_iterator first, typename underlying_type::basic_iterator last) override { + for(; first != last; ++first) { + // cannot use first::index() because it would break with cross iterators + const auto pos = base_type::index(*first); + base_type::in_place_pop(first, first + 1u); + std::destroy_at(std::addressof(element_at(pos))); } + } - inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this == other); + /** + * @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. + */ + typename underlying_type::basic_iterator try_emplace([[maybe_unused]] const Entity entt, const bool force_back, const void *value) override { + if(value) { + if constexpr(std::is_copy_constructible_v) { + return emplace_element(entt, force_back, *static_cast(value)); + } else { + return base_type::end(); + } + } else { + if constexpr(std::is_default_constructible_v) { + return emplace_element(entt, force_back); + } else { + return base_type::end(); + } } + } - bool operator<(const iterator &other) const ENTT_NOEXCEPT { - return index > other.index; - } +public: + /*! @brief Base type. */ + using base_type = underlying_type; + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Type of the objects assigned to entities. */ + using value_type = Type; + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Pointer type to contained elements. */ + using pointer = typename container_type::pointer; + /*! @brief Constant pointer type to contained elements. */ + using const_pointer = typename 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 = std::reverse_iterator; + /*! @brief Constant reverse iterator type. */ + using const_reverse_iterator = std::reverse_iterator; + /*! @brief Extended iterable storage proxy. */ + using iterable = iterable_adaptor>; + /*! @brief Constant extended iterable storage proxy. */ + using const_iterable = iterable_adaptor>; + + /*! @brief Default constructor. */ + basic_storage() + : basic_storage{allocator_type{}} {} - bool operator>(const iterator &other) const ENTT_NOEXCEPT { - return index < other.index; - } + /** + * @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(), deletion_policy{comp_traits::in_place_delete}, allocator}, + packed{container_type{allocator}, allocator} {} - inline bool operator<=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this > other); - } + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_storage(basic_storage &&other) ENTT_NOEXCEPT + : base_type{std::move(other)}, + packed{std::move(other.packed)} {} - inline bool operator>=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this < other); - } + /** + * @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) ENTT_NOEXCEPT + : base_type{std::move(other), allocator}, + packed{container_type{std::move(other.packed.first()), allocator}, allocator} { + ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.second() == other.packed.second(), "Copying a storage is not allowed"); + } - pointer operator->() const ENTT_NOEXCEPT { - const auto pos = size_type(index-1); - return &(*instances)[pos]; - } + /*! @brief Default destructor. */ + ~basic_storage() override { + shrink_to_size(0u); + } - inline reference operator*() const ENTT_NOEXCEPT { - return *operator->(); - } + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This storage. + */ + basic_storage &operator=(basic_storage &&other) ENTT_NOEXCEPT { + ENTT_ASSERT(alloc_traits::is_always_equal::value || packed.second() == other.packed.second(), "Copying a storage is not allowed"); + + shrink_to_size(0u); + base_type::operator=(std::move(other)); + packed.first() = std::move(other.packed.first()); + propagate_on_container_move_assignment(packed.second(), other.packed.second()); + return *this; + } - private: - instance_type *instances; - index_type index; - }; + /** + * @brief Exchanges the contents with those of a given storage. + * @param other Storage to exchange the content with. + */ + void swap(basic_storage &other) { + using std::swap; + underlying_type::swap(other); + propagate_on_container_swap(packed.second(), other.packed.second()); + swap(packed.first(), other.packed.first()); + } -public: - /*! @brief Type of the objects associated with the entities. */ - using object_type = Type; - /*! @brief Underlying entity identifier. */ - using entity_type = typename underlying_type::entity_type; - /*! @brief Unsigned integer type. */ - using size_type = typename underlying_type::size_type; - /*! @brief Random access iterator type. */ - using iterator_type = iterator; - /*! @brief Constant random access iterator type. */ - using const_iterator_type = iterator; + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return allocator_type{packed.second()}; + } /** * @brief Increases the capacity of a storage. @@ -174,544 +487,428 @@ public: * @param cap Desired capacity. */ void reserve(const size_type cap) override { - underlying_type::reserve(cap); - instances.reserve(cap); + 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 ENTT_NOEXCEPT override { + return packed.first().size() * comp_traits::page_size; } /*! @brief Requests the removal of unused capacity. */ void shrink_to_fit() override { - underlying_type::shrink_to_fit(); - instances.shrink_to_fit(); + base_type::shrink_to_fit(); + shrink_to_size(base_type::size()); } /** * @brief Direct access to the array of objects. - * - * The returned pointer is such that range `[raw(), raw() + size()]` is - * always a valid range, even if the container is empty. - * - * @note - * There are no guarantees on the order, even though either `sort` or - * `respect` has been previously invoked. Internal data structures arrange - * elements to maximize performance. Accessing them directly gives a - * performance boost but less guarantees. Use `begin` and `end` if you want - * to iterate the storage in the expected order. - * * @return A pointer to the array of objects. */ - const object_type * raw() const ENTT_NOEXCEPT { - return instances.data(); + [[nodiscard]] const_pointer raw() const ENTT_NOEXCEPT { + return packed.first().data(); } /*! @copydoc raw */ - object_type * raw() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).raw()); + [[nodiscard]] pointer raw() ENTT_NOEXCEPT { + return packed.first().data(); } /** * @brief Returns an iterator to the beginning. * - * The returned iterator points to the first instance of the given type. If - * the storage is empty, the returned iterator will be equal to `end()`. - * - * @note - * Input iterators stay true to the order imposed by a call to either `sort` - * or `respect`. + * The returned iterator points to the first instance of the internal array. + * If the storage is empty, the returned iterator will be equal to `end()`. * - * @return An iterator to the first instance of the given type. + * @return An iterator to the first instance of the internal array. */ - const_iterator_type cbegin() const ENTT_NOEXCEPT { - const typename traits_type::difference_type pos = underlying_type::size(); - return const_iterator_type{&instances, pos}; + [[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT { + const auto pos = static_cast(base_type::size()); + return const_iterator{&packed.first(), pos}; } /*! @copydoc cbegin */ - inline const_iterator_type begin() const ENTT_NOEXCEPT { + [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT { return cbegin(); } /*! @copydoc begin */ - iterator_type begin() ENTT_NOEXCEPT { - const typename traits_type::difference_type pos = underlying_type::size(); - return iterator_type{&instances, pos}; + [[nodiscard]] iterator begin() ENTT_NOEXCEPT { + const auto pos = static_cast(base_type::size()); + return iterator{&packed.first(), pos}; } /** * @brief Returns an iterator to the end. * * The returned iterator points to the element following the last instance - * of the given type. Attempting to dereference the returned iterator + * of the internal array. Attempting to dereference the returned iterator * results in undefined behavior. * - * @note - * Input iterators stay true to the order imposed by a call to either `sort` - * or `respect`. - * * @return An iterator to the element following the last instance of the - * given type. + * internal array. */ - const_iterator_type cend() const ENTT_NOEXCEPT { - return const_iterator_type{&instances, {}}; + [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { + return const_iterator{&packed.first(), {}}; } /*! @copydoc cend */ - inline const_iterator_type end() const ENTT_NOEXCEPT { + [[nodiscard]] const_iterator end() const ENTT_NOEXCEPT { return cend(); } /*! @copydoc end */ - iterator_type end() ENTT_NOEXCEPT { - return iterator_type{&instances, {}}; + [[nodiscard]] iterator end() ENTT_NOEXCEPT { + return iterator{&packed.first(), {}}; + } + + /** + * @brief Returns a reverse iterator to the beginning. + * + * The returned iterator points to the first instance of the reversed + * internal array. 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 ENTT_NOEXCEPT { + return std::make_reverse_iterator(cend()); + } + + /*! @copydoc crbegin */ + [[nodiscard]] const_reverse_iterator rbegin() const ENTT_NOEXCEPT { + return crbegin(); + } + + /*! @copydoc rbegin */ + [[nodiscard]] reverse_iterator rbegin() ENTT_NOEXCEPT { + return std::make_reverse_iterator(end()); + } + + /** + * @brief Returns a reverse iterator to the end. + * + * The returned iterator points to the element following the last instance + * of the reversed internal array. Attempting to dereference the returned + * iterator results in undefined behavior. + * + * @return An iterator to the element following the last instance of the + * reversed internal array. + */ + [[nodiscard]] const_reverse_iterator crend() const ENTT_NOEXCEPT { + return std::make_reverse_iterator(cbegin()); + } + + /*! @copydoc crend */ + [[nodiscard]] const_reverse_iterator rend() const ENTT_NOEXCEPT { + return crend(); + } + + /*! @copydoc rend */ + [[nodiscard]] reverse_iterator rend() ENTT_NOEXCEPT { + return std::make_reverse_iterator(begin()); } /** - * @brief Returns the object associated with an entity. + * @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.
- * An assertion will abort the execution at runtime in debug mode if the - * storage doesn't contain the given entity. + * undefined behavior. * - * @param entt A valid entity identifier. - * @return The object associated with the entity. + * @param entt A valid identifier. + * @return The object assigned to the entity. */ - const object_type & get(const entity_type entt) const ENTT_NOEXCEPT { - return instances[underlying_type::get(entt)]; + [[nodiscard]] const value_type &get(const entity_type entt) const ENTT_NOEXCEPT { + return element_at(base_type::index(entt)); } /*! @copydoc get */ - inline object_type & get(const entity_type entt) ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).get(entt)); + [[nodiscard]] value_type &get(const entity_type entt) ENTT_NOEXCEPT { + return const_cast(std::as_const(*this).get(entt)); } /** - * @brief Returns a pointer to the object associated with an entity, if any. - * @param entt A valid entity identifier. - * @return The object associated with the entity, if any. + * @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. */ - const object_type * try_get(const entity_type entt) const ENTT_NOEXCEPT { - return underlying_type::has(entt) ? (instances.data() + underlying_type::get(entt)) : nullptr; + [[nodiscard]] std::tuple get_as_tuple(const entity_type entt) const ENTT_NOEXCEPT { + return std::forward_as_tuple(get(entt)); } - /*! @copydoc try_get */ - inline object_type * try_get(const entity_type entt) ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).try_get(entt)); + /*! @copydoc get_as_tuple */ + [[nodiscard]] std::tuple get_as_tuple(const entity_type entt) ENTT_NOEXCEPT { + return std::forward_as_tuple(get(entt)); } /** * @brief Assigns an entity to a storage and constructs its object. * - * This version accept both types that can be constructed in place directly - * and types like aggregates that do not work well with a placement new as - * performed usually under the hood during an _emplace back_. - * * @warning * Attempting to use an entity that already belongs to the storage results - * in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * storage already contains the given entity. + * in undefined behavior. * * @tparam Args Types of arguments to use to construct the object. - * @param entt A valid entity identifier. + * @param entt A valid identifier. * @param args Parameters to use to construct an object for the entity. - * @return The object associated with the entity. + * @return A reference to the newly created object. */ template - object_type & construct(const entity_type entt, Args &&... args) { - if constexpr(std::is_aggregate_v) { - instances.emplace_back(Type{std::forward(args)...}); + value_type &emplace(const entity_type entt, Args &&...args) { + if constexpr(std::is_aggregate_v) { + const auto it = emplace_element(entt, false, Type{std::forward(args)...}); + return element_at(static_cast(it.index())); } else { - instances.emplace_back(std::forward(args)...); + const auto it = emplace_element(entt, false, std::forward(args)...); + return element_at(static_cast(it.index())); } + } - // entity goes after component in case constructor throws - underlying_type::construct(entt); - return instances.back(); + /** + * @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); + (std::forward(func)(elem), ...); + return elem; } /** * @brief Assigns one or more entities to a storage and constructs their - * objects. - * - * The object type must be at least default constructible. + * objects from a given instance. * * @warning * Attempting to assign an entity that already belongs to the storage - * results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * storage already contains the given entity. + * results in undefined behavior. * - * @tparam It Type of forward iterator. + * @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 A pointer to the array of instances just created and sorted the - * same of the entities. + * @param value An instance of the object to construct. */ template - object_type * batch(It first, It last) { - static_assert(std::is_default_constructible_v); - const auto skip = instances.size(); - instances.insert(instances.end(), last-first, {}); - // entity goes after component in case constructor throws - underlying_type::batch(first, last); - return instances.data() + skip; - } - - /** - * @brief Removes an entity from a storage and destroys its object. - * - * @warning - * Attempting to use an entity that doesn't belong to the storage results in - * undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * storage doesn't contain the given entity. - * - * @param entt A valid entity identifier. - */ - void destroy(const entity_type entt) override { - std::swap(instances[underlying_type::get(entt)], instances.back()); - instances.pop_back(); - underlying_type::destroy(entt); + void insert(It first, It last, const value_type &value = {}) { + for(; first != last; ++first) { + emplace_element(*first, true, value); + } } /** - * @brief Swaps entities and objects in the internal packed arrays. + * @brief Assigns one or more entities to a storage and constructs their + * objects from a given range. * - * @warning - * Attempting to swap entities that don't belong to the sparse set results - * in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * sparse set doesn't contain the given entities. + * @sa construct * - * @param lhs A valid position within the sparse set. - * @param rhs A valid position within the sparse set. + * @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 objects. */ - void swap(const size_type lhs, const size_type rhs) ENTT_NOEXCEPT { - ENTT_ASSERT(lhs < instances.size()); - ENTT_ASSERT(rhs < instances.size()); - std::swap(instances[lhs], instances[rhs]); - underlying_type::swap(lhs, rhs); - } - - /** - * @brief Sort instances according to the given comparison function. - * - * Sort the elements so that iterating the storage with a couple of - * iterators returns them in the expected order. See `begin` and `end` for - * more details. - * - * 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(const Entity, const Entity); - * bool(const Type &, const Type &); - * @endcode - * - * Moreover, the comparison function object shall induce a - * _strict weak ordering_ on the values. - * - * The sort function oject 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. - * - * The comparison function object received by the sort function object - * hasn't necessarily the type of the one passed along with the other - * parameters to this member function. - * - * @note - * Attempting to iterate elements using a raw pointer returned by a call to - * either `data` or `raw` gives no guarantees on the order, even though - * `sort` has been invoked. - * - * @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) { - std::vector copy(instances.size()); - std::iota(copy.begin(), copy.end(), 0); - - if constexpr(std::is_invocable_v) { - static_assert(!std::is_empty_v); - - algo(copy.rbegin(), copy.rend(), [this, compare = std::move(compare)](const auto lhs, const auto rhs) { - return compare(std::as_const(instances[lhs]), std::as_const(instances[rhs])); - }, std::forward(args)...); - } else { - algo(copy.rbegin(), copy.rend(), [compare = std::move(compare), data = underlying_type::data()](const auto lhs, const auto rhs) { - return compare(data[lhs], data[rhs]); - }, std::forward(args)...); - } - - for(size_type pos{}, last = copy.size(); pos < last; ++pos) { - auto curr = pos; - auto next = copy[curr]; - - while(curr != next) { - const auto lhs = copy[curr]; - const auto rhs = copy[next]; - std::swap(instances[lhs], instances[rhs]); - underlying_type::swap(lhs, rhs); - copy[curr] = curr; - curr = next; - next = copy[curr]; - } + template::value_type, value_type>>> + void insert(EIt first, EIt last, CIt from) { + for(; first != last; ++first, ++from) { + emplace_element(*first, true, *from); } } /** - * @brief Sort instances according to the order of the entities in another - * sparse set. - * - * Entities that are part of both the storage are ordered internally - * according to the order they have in `other`. All the other entities goes - * to the end of the list and there are no guarantess on their order. - * Instances are sorted according to the entities to which they belong.
- * In other terms, this function can be used to impose the same order on two - * sets by using one of them as a master and the other one as a slave. - * - * Iterating the storage with a couple of iterators returns elements in the - * expected order after a call to `respect`. See `begin` and `end` for more - * details. + * @brief Returns an iterable object to use to _visit_ a storage. * - * @note - * Attempting to iterate elements using a raw pointer returned by a call to - * either `data` or `raw` gives no guarantees on the order, even though - * `respect` has been invoked. + * The iterable object returns a tuple that contains the current entity and + * a reference to its component. * - * @param other The sparse sets that imposes the order of the entities. + * @return An iterable object to use to _visit_ the storage. */ - void respect(const sparse_set &other) ENTT_NOEXCEPT override { - const auto to = other.end(); - auto from = other.begin(); - - size_type pos = underlying_type::size() - 1; - const auto *local = underlying_type::data(); - - while(pos && from != to) { - const auto curr = *from; - - if(underlying_type::has(curr)) { - if(curr != *(local + pos)) { - auto candidate = underlying_type::get(curr); - std::swap(instances[pos], instances[candidate]); - underlying_type::swap(pos, candidate); - } - - --pos; - } - - ++from; - } + [[nodiscard]] iterable each() ENTT_NOEXCEPT { + return {internal::extended_storage_iterator{base_type::begin(), begin()}, internal::extended_storage_iterator{base_type::end(), end()}}; } - /*! @brief Resets a storage. */ - void reset() override { - underlying_type::reset(); - instances.clear(); + /*! @copydoc each */ + [[nodiscard]] const_iterable each() const ENTT_NOEXCEPT { + return {internal::extended_storage_iterator{base_type::cbegin(), cbegin()}, internal::extended_storage_iterator{base_type::cend(), cend()}}; } private: - std::vector instances; + compressed_pair packed; }; - /*! @copydoc basic_storage */ -template -class basic_storage>>: public sparse_set { - using underlying_type = sparse_set; - using traits_type = entt_traits; - - class iterator { - friend class basic_storage; - - using index_type = typename traits_type::difference_type; - - iterator(const index_type idx) ENTT_NOEXCEPT - : index{idx} - {} - - public: - using difference_type = index_type; - using value_type = Type; - using pointer = const value_type *; - using reference = value_type; - using iterator_category = std::input_iterator_tag; - - iterator() ENTT_NOEXCEPT = default; - - iterator & operator++() ENTT_NOEXCEPT { - return --index, *this; - } - - iterator operator++(int) ENTT_NOEXCEPT { - iterator orig = *this; - return ++(*this), orig; - } - - iterator & operator--() ENTT_NOEXCEPT { - return ++index, *this; - } - - iterator operator--(int) ENTT_NOEXCEPT { - iterator orig = *this; - return --(*this), orig; - } - - iterator & operator+=(const difference_type value) ENTT_NOEXCEPT { - index -= value; - return *this; - } - - iterator operator+(const difference_type value) const ENTT_NOEXCEPT { - return iterator{index-value}; - } +template +class basic_storage>> + : public basic_sparse_set::template rebind_alloc> { + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using underlying_type = basic_sparse_set>; + using comp_traits = component_traits; - inline iterator & operator-=(const difference_type value) ENTT_NOEXCEPT { - return (*this += -value); - } - - inline iterator operator-(const difference_type value) const ENTT_NOEXCEPT { - return (*this + -value); - } - - difference_type operator-(const iterator &other) const ENTT_NOEXCEPT { - return other.index - index; - } - - reference operator[](const difference_type) const ENTT_NOEXCEPT { - return {}; - } - - bool operator==(const iterator &other) const ENTT_NOEXCEPT { - return other.index == index; - } - - inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this == other); - } - - bool operator<(const iterator &other) const ENTT_NOEXCEPT { - return index > other.index; - } - - bool operator>(const iterator &other) const ENTT_NOEXCEPT { - return index < other.index; - } +public: + /*! @brief Base type. */ + using base_type = underlying_type; + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Type of the objects assigned to entities. */ + using value_type = Type; + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Extended iterable storage proxy. */ + using iterable = iterable_adaptor>; + /*! @brief Constant extended iterable storage proxy. */ + using const_iterable = iterable_adaptor>; - inline bool operator<=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this > other); - } + /*! @brief Default constructor. */ + basic_storage() + : basic_storage{allocator_type{}} {} - inline bool operator>=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this < other); - } + /** + * @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(), deletion_policy{comp_traits::in_place_delete}, allocator} {} - pointer operator->() const ENTT_NOEXCEPT { - return nullptr; - } + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_storage(basic_storage &&other) ENTT_NOEXCEPT = default; - inline reference operator*() const ENTT_NOEXCEPT { - return {}; - } + /** + * @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) ENTT_NOEXCEPT + : base_type{std::move(other), allocator} {} - private: - index_type index; - }; + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This storage. + */ + basic_storage &operator=(basic_storage &&other) ENTT_NOEXCEPT = default; -public: - /*! @brief Type of the objects associated with the entities. */ - using object_type = Type; - /*! @brief Underlying entity identifier. */ - using entity_type = typename underlying_type::entity_type; - /*! @brief Unsigned integer type. */ - using size_type = typename underlying_type::size_type; - /*! @brief Random access iterator type. */ - using iterator_type = iterator; + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return allocator_type{base_type::get_allocator()}; + } /** - * @brief Returns an iterator to the beginning. - * - * The returned iterator points to the first instance of the given type. If - * the storage is empty, the returned iterator will be equal to `end()`. + * @brief Returns the object assigned to an entity, that is `void`. * - * @note - * Input iterators stay true to the order imposed by a call to either `sort` - * or `respect`. + * @warning + * Attempting to use an entity that doesn't belong to the storage results in + * undefined behavior. * - * @return An iterator to the first instance of the given type. + * @param entt A valid identifier. */ - iterator_type cbegin() const ENTT_NOEXCEPT { - const typename traits_type::difference_type pos = underlying_type::size(); - return iterator_type{pos}; + void get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { + ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity"); } - /*! @copydoc cbegin */ - inline iterator_type begin() const ENTT_NOEXCEPT { - return cbegin(); + /** + * @brief Returns an empty tuple. + * + * @warning + * Attempting to use an entity that doesn't belong to the storage results in + * undefined behavior. + * + * @param entt A valid identifier. + * @return Returns an empty tuple. + */ + [[nodiscard]] std::tuple<> get_as_tuple([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { + ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity"); + return std::tuple{}; } /** - * @brief Returns an iterator to the end. - * - * The returned iterator points to the element following the last instance - * of the given type. Attempting to dereference the returned iterator - * results in undefined behavior. + * @brief Assigns an entity to a storage and constructs its object. * - * @note - * Input iterators stay true to the order imposed by a call to either `sort` - * or `respect`. + * @warning + * Attempting to use an entity that already belongs to the storage results + * in undefined behavior. * - * @return An iterator to the element following the last instance of the - * given type. + * @tparam Args Types of arguments to use to construct the object. + * @param entt A valid identifier. */ - iterator_type cend() const ENTT_NOEXCEPT { - return iterator_type{}; + template + void emplace(const entity_type entt, Args &&...) { + base_type::try_emplace(entt, false); } - /*! @copydoc cend */ - inline iterator_type end() const ENTT_NOEXCEPT { - return cend(); + /** + * @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), "Storage does not contain entity"); + (std::forward(func)(), ...); } /** - * @brief Returns the object associated with an entity. - * - * @note - * Empty types aren't explicitly instantiated. Therefore, this function - * always returns a temporary object. + * @brief Assigns entities to a storage. + * @tparam It Type of input iterator. + * @tparam Args Types of optional arguments. + * @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 insert(It first, It last, Args &&...) { + for(; first != last; ++first) { + base_type::try_emplace(*first, true); + } + } + + /** + * @brief Returns an iterable object to use to _visit_ a storage. * - * @warning - * Attempting to use an entity that doesn't belong to the storage results in - * undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * storage doesn't contain the given entity. + * The iterable object returns a tuple that contains the current entity. * - * @param entt A valid entity identifier. - * @return The object associated with the entity. + * @return An iterable object to use to _visit_ the storage. */ - object_type get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { - ENTT_ASSERT(underlying_type::has(entt)); - return {}; + [[nodiscard]] iterable each() ENTT_NOEXCEPT { + return {internal::extended_storage_iterator{base_type::begin()}, internal::extended_storage_iterator{base_type::end()}}; } -}; - -/*! @copydoc basic_storage */ -template -struct storage: basic_storage {}; + /*! @copydoc each */ + [[nodiscard]] const_iterable each() const ENTT_NOEXCEPT { + return {internal::extended_storage_iterator{base_type::cbegin()}, internal::extended_storage_iterator{base_type::cend()}}; + } +}; -} +/** + * @brief Provides a common way to access certain properties of storage types. + * @tparam Entity A valid entity type (see entt_traits for more details). + * @tparam Type Type of objects managed by the storage class. + */ +template +struct storage_traits { + /*! @brief Resulting type after component-to-storage conversion. */ + using storage_type = sigh_storage_mixin>; +}; +} // namespace entt -#endif // ENTT_ENTITY_STORAGE_HPP +#endif diff --git a/modules/entt/src/entt/entity/utility.hpp b/modules/entt/src/entt/entity/utility.hpp new file mode 100644 index 0000000..902805d --- /dev/null +++ b/modules/entt/src/entt/entity/utility.hpp @@ -0,0 +1,52 @@ +#ifndef ENTT_ENTITY_UTILITY_HPP +#define ENTT_ENTITY_UTILITY_HPP + +#include "../core/type_traits.hpp" + +namespace entt { + +/** + * @brief Alias for exclusion lists. + * @tparam Type List of types. + */ +template +struct exclude_t: type_list {}; + +/** + * @brief Variable template for exclusion lists. + * @tparam Type List of types. + */ +template +inline constexpr exclude_t exclude{}; + +/** + * @brief Alias for lists of observed components. + * @tparam Type List of types. + */ +template +struct get_t: type_list {}; + +/** + * @brief Variable template for lists of observed components. + * @tparam Type List of types. + */ +template +inline constexpr get_t get{}; + +/** + * @brief Alias for lists of owned components. + * @tparam Type List of types. + */ +template +struct owned_t: type_list {}; + +/** + * @brief Variable template for lists of owned components. + * @tparam Type List of types. + */ +template +inline constexpr owned_t owned{}; + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/entity/view.hpp b/modules/entt/src/entt/entity/view.hpp index b3d51da..aaa6de2 100644 --- a/modules/entt/src/entt/entity/view.hpp +++ b/modules/entt/src/entt/entity/view.hpp @@ -1,439 +1,450 @@ #ifndef ENTT_ENTITY_VIEW_HPP #define ENTT_ENTITY_VIEW_HPP - -#include +#include #include +#include #include -#include -#include #include +#include #include "../config/config.h" +#include "../core/iterator.hpp" #include "../core/type_traits.hpp" -#include "sparse_set.hpp" -#include "storage.hpp" +#include "component.hpp" #include "entity.hpp" #include "fwd.hpp" - +#include "sparse_set.hpp" +#include "storage.hpp" +#include "utility.hpp" namespace entt { - /** - * @brief Multi component view. - * - * Multi component views iterate over those entities that have at least all the - * given components in their bags. During initialization, a multi component view - * looks at the number of entities available for each component and picks up a - * reference to the smallest set of candidate entities in order to get a - * performance boost when iterate.
- * Order of elements during iterations are highly dependent on the order of the - * underlying data structures. See sparse_set and its specializations for more - * details. - * - * @b Important - * - * Iterators aren't invalidated if: - * - * * New instances of the given components are created and assigned to entities. - * * The entity currently pointed is modified (as an example, if one of the - * given components is removed from the entity to which the iterator points). - * - * In all the other cases, modifying the pools of the given components in any - * way invalidates all the iterators and using them results in undefined - * behavior. - * - * @note - * Views share references to the underlying data structures of the registry that - * generated them. Therefore any change to the entities and to the components - * made by means of the registry are immediately reflected by views. - * - * @warning - * Lifetime of a view must overcome the one of the registry that generated it. - * In any other case, attempting to use a view results in undefined behavior. - * - * @tparam Entity A valid entity type (see entt_traits for more details). - * @tparam Component Types of components iterated by the view. + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. */ -template -class basic_view { - static_assert(sizeof...(Component) > 1); - /*! @brief A registry is allowed to create views. */ - friend class basic_registry; +namespace internal { - template - using pool_type = std::conditional_t, const storage>, storage>; +template +class view_iterator final { + using iterator_type = typename Type::const_iterator; - template - using component_iterator_type = decltype(std::declval>().begin()); + [[nodiscard]] bool valid() const ENTT_NOEXCEPT { + return ((Component != 0u) || (*it != tombstone)) + && std::apply([entt = *it](const auto *...curr) { return (curr->contains(entt) && ...); }, pools) + && std::apply([entt = *it](const auto *...curr) { return (!curr->contains(entt) && ...); }, filter); + } - using underlying_iterator_type = typename sparse_set::iterator_type; - using unchecked_type = std::array *, (sizeof...(Component) - 1)>; - using traits_type = entt_traits; +public: + using value_type = typename iterator_type::value_type; + using pointer = typename iterator_type::pointer; + using reference = typename iterator_type::reference; + using difference_type = typename iterator_type::difference_type; + using iterator_category = std::forward_iterator_tag; + + view_iterator() ENTT_NOEXCEPT = default; + + view_iterator(iterator_type curr, iterator_type to, std::array all_of, std::array none_of) ENTT_NOEXCEPT + : it{curr}, + last{to}, + pools{all_of}, + filter{none_of} { + if(it != last && !valid()) { + ++(*this); + } + } - class iterator { - friend class basic_view; + view_iterator &operator++() ENTT_NOEXCEPT { + while(++it != last && !valid()) {} + return *this; + } - using extent_type = typename sparse_set::size_type; + view_iterator operator++(int) ENTT_NOEXCEPT { + view_iterator orig = *this; + return ++(*this), orig; + } - iterator(unchecked_type other, underlying_iterator_type first, underlying_iterator_type last) ENTT_NOEXCEPT - : unchecked{other}, - begin{first}, - end{last}, - extent{min(std::make_index_sequence{})} - { - if(begin != end && !valid()) { - ++(*this); - } - } + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return &*it; + } - template - extent_type min(std::index_sequence) const ENTT_NOEXCEPT { - return std::min({ std::get(unchecked)->extent()... }); - } + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return *operator->(); + } - bool valid() const ENTT_NOEXCEPT { - const auto entt = *begin; - const auto sz = size_type(entt& traits_type::entity_mask); + template + friend bool operator==(const view_iterator &, const view_iterator &) ENTT_NOEXCEPT; - return sz < extent && std::all_of(unchecked.cbegin(), unchecked.cend(), [entt](const sparse_set *view) { - return view->has(entt); - }); - } +private: + iterator_type it; + iterator_type last; + std::array pools; + std::array filter; +}; - public: - using difference_type = typename underlying_iterator_type::difference_type; - using value_type = typename underlying_iterator_type::value_type; - using pointer = typename underlying_iterator_type::pointer; - using reference = typename underlying_iterator_type::reference; - using iterator_category = std::forward_iterator_tag; +template +[[nodiscard]] bool operator==(const view_iterator &lhs, const view_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} - iterator() ENTT_NOEXCEPT = default; +template +[[nodiscard]] bool operator!=(const view_iterator &lhs, const view_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} - iterator & operator++() ENTT_NOEXCEPT { - return (++begin != end && !valid()) ? ++(*this) : *this; - } +template +struct extended_view_iterator final { + using difference_type = std::ptrdiff_t; + using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval()), std::declval().get_as_tuple({})...)); + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; - iterator operator++(int) ENTT_NOEXCEPT { - iterator orig = *this; - return ++(*this), orig; - } + extended_view_iterator() = default; - bool operator==(const iterator &other) const ENTT_NOEXCEPT { - return other.begin == begin; - } + extended_view_iterator(It from, std::tuple storage) + : it{from}, + pools{storage} {} - inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT { - return !(*this == other); - } + extended_view_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } - pointer operator->() const ENTT_NOEXCEPT { - return begin.operator->(); - } + extended_view_iterator operator++(int) ENTT_NOEXCEPT { + extended_view_iterator orig = *this; + return ++(*this), orig; + } - inline reference operator*() const ENTT_NOEXCEPT { - return *operator->(); - } + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return std::apply([entt = *it](auto *...curr) { return std::tuple_cat(std::make_tuple(entt), curr->get_as_tuple(entt)...); }, pools); + } - private: - unchecked_type unchecked; - underlying_iterator_type begin; - underlying_iterator_type end; - extent_type extent; - }; + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } - // we could use pool_type *..., but vs complains about it and refuses to compile for unknown reasons (likely a bug) - basic_view(storage> *... ref) ENTT_NOEXCEPT - : pools{ref...} - {} + template + friend bool operator==(const extended_view_iterator &, const extended_view_iterator &) ENTT_NOEXCEPT; - const sparse_set * candidate() const ENTT_NOEXCEPT { - return std::min({ static_cast *>(std::get *>(pools))... }, [](const auto *lhs, const auto *rhs) { - return lhs->size() < rhs->size(); - }); - } +private: + It it; + std::tuple pools; +}; + +template +[[nodiscard]] bool operator==(const extended_view_iterator &lhs, const extended_view_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] bool operator!=(const extended_view_iterator &lhs, const extended_view_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @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. + */ +template +class basic_view; + +/** + * @brief Multi component view. + * + * Multi component views iterate over those entities that have at least all the + * given components in their bags. During initialization, a multi component view + * looks at the number of entities available for each component and uses the + * smallest set in order to get a performance boost when iterate. + * + * @b Important + * + * Iterators aren't invalidated if: + * + * * New instances of the given components are created and assigned to entities. + * * The entity currently pointed is modified (as an example, if one of the + * given components is removed from the entity to which the iterator points). + * * The entity currently pointed is destroyed. + * + * In all other cases, modifying the pools iterated by the view in any way + * invalidates all the iterators and using them results in undefined behavior. + * + * @tparam Entity A valid entity type (see entt_traits for more details). + * @tparam Component Types of components iterated by the view. + * @tparam Exclude Types of components used to filter the view. + */ +template +class basic_view, exclude_t> { + template + friend class basic_view; - unchecked_type unchecked(const sparse_set *view) const ENTT_NOEXCEPT { - unchecked_type other{}; - typename unchecked_type::size_type pos{}; - ((std::get *>(pools) == view ? nullptr : (other[pos++] = std::get *>(pools))), ...); + template + using storage_type = constness_as_t>::storage_type, Comp>; + + template + [[nodiscard]] auto pools_to_array(std::index_sequence) const ENTT_NOEXCEPT { + std::size_t pos{}; + std::array other{}; + (static_cast(std::get(pools) == view ? void() : void(other[pos++] = std::get(pools))), ...); return other; } - template - inline decltype(auto) get([[maybe_unused]] component_iterator_type it, [[maybe_unused]] pool_type *cpool, [[maybe_unused]] const Entity entt) const ENTT_NOEXCEPT { - if constexpr(std::is_same_v) { - return *it; + template + [[nodiscard]] auto dispatch_get(const std::tuple &curr) const { + if constexpr(Comp == Other) { + return std::forward_as_tuple(std::get(curr)...); } else { - return cpool->get(entt); + return std::get(pools)->get_as_tuple(std::get<0>(curr)); } } - template - void traverse(Func func, type_list, type_list) const { - const auto end = std::get *>(pools)->sparse_set::end(); - auto begin = std::get *>(pools)->sparse_set::begin(); - - if constexpr(std::disjunction_v...>) { - std::for_each(begin, end, [raw = std::get *>(pools)->begin(), &func, this](const auto entity) mutable { - auto curr = raw++; - - if((std::get *>(pools)->has(entity) && ...)) { - if constexpr(std::is_invocable_v({}))...>) { - func(get(curr, std::get *>(pools), entity)...); - } else { - func(entity, get(curr, std::get *>(pools), entity)...); - } + template + void each(Func func, std::index_sequence) const { + for(const auto curr: std::get(pools)->each()) { + const auto entt = std::get<0>(curr); + + if(((sizeof...(Component) != 1u) || (entt != tombstone)) + && ((Comp == Index || std::get(pools)->contains(entt)) && ...) + && std::apply([entt](const auto *...cpool) { return (!cpool->contains(entt) && ...); }, filter)) { + if constexpr(is_applicable_v{}, std::declval().get({})))>) { + std::apply(func, std::tuple_cat(std::make_tuple(entt), dispatch_get(curr)...)); + } else { + std::apply(func, std::tuple_cat(dispatch_get(curr)...)); } - }); - } else { - std::for_each(begin, end, [&func, this](const auto entity) mutable { - if((std::get *>(pools)->has(entity) && ...)) { - if constexpr(std::is_invocable_v({}))...>) { - func(std::get *>(pools)->get(entity)...); - } else { - func(entity, std::get *>(pools)->get(entity)...); - } - } - }); + } } } + template + void pick_and_each(Func func, std::index_sequence seq) const { + ((std::get(pools) == view ? each(std::move(func), seq) : void()), ...); + } + public: /*! @brief Underlying entity identifier. */ - using entity_type = typename sparse_set::entity_type; + using entity_type = Entity; /*! @brief Unsigned integer type. */ - using size_type = typename sparse_set::size_type; - /*! @brief Input iterator type. */ - using iterator_type = iterator; + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using base_type = std::common_type_t::base_type...>; + /*! @brief Bidirectional iterator type. */ + using iterator = internal::view_iterator; + /*! @brief Iterable view type. */ + using iterable = iterable_adaptor...>>; + + /*! @brief Default constructor to use to create empty, invalid views. */ + basic_view() ENTT_NOEXCEPT + : pools{}, + filter{}, + view{} {} /** - * @brief Returns the number of existing components of the given type. - * @tparam Comp Type of component of which to return the size. - * @return Number of existing components of the given type. + * @brief Constructs a multi-type view from a set of storage classes. + * @param component The storage for the types to iterate. + * @param epool The storage for the types used to filter the view. + */ + basic_view(storage_type &...component, const storage_type &...epool) ENTT_NOEXCEPT + : pools{&component...}, + filter{&epool...}, + view{(std::min)({&static_cast(component)...}, [](auto *lhs, auto *rhs) { return lhs->size() < rhs->size(); })} {} + + /** + * @brief Creates a new view driven by a given component in its iterations. + * @tparam Comp Type of component used to drive the iteration. + * @return A new view driven by the given component in its iterations. */ template - size_type size() const ENTT_NOEXCEPT { - return std::get *>(pools)->size(); + [[nodiscard]] basic_view use() const ENTT_NOEXCEPT { + basic_view other{*this}; + other.view = std::get *>(pools); + return other; } /** - * @brief Estimates the number of entities that have the given components. - * @return Estimated number of entities that have the given components. + * @brief Creates a new view driven by a given component in its iterations. + * @tparam Comp Index of the component used to drive the iteration. + * @return A new view driven by the given component in its iterations. */ - size_type size() const ENTT_NOEXCEPT { - return std::min({ std::get *>(pools)->size()... }); + template + [[nodiscard]] basic_view use() const ENTT_NOEXCEPT { + basic_view other{*this}; + other.view = std::get(pools); + return other; } /** - * @brief Checks whether the pool of a given component is empty. - * @tparam Comp Type of component in which one is interested. - * @return True if the pool of the given component is empty, false - * otherwise. + * @brief Returns the leading storage of a view. + * @return The leading storage of the view. + */ + const base_type &handle() const ENTT_NOEXCEPT { + return *view; + } + + /** + * @brief Returns the storage for a given component type. + * @tparam Comp Type of component of which to return the storage. + * @return The storage for the given component type. */ template - bool empty() const ENTT_NOEXCEPT { - return std::get *>(pools)->empty(); + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get *>(pools); } /** - * @brief Checks if the view is definitely empty. - * @return True if the view is definitely empty, false otherwise. + * @brief Returns the storage for a given component type. + * @tparam Comp Index of component of which to return the storage. + * @return The storage for the given component type. */ - bool empty() const ENTT_NOEXCEPT { - return (std::get *>(pools)->empty() || ...); + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get(pools); } /** - * @brief Direct access to the list of components of a given pool. - * - * The returned pointer is such that range - * `[raw(), raw() + size()]` is always a valid range, even - * if the container is empty. - * - * @note - * There are no guarantees on the order of the components. Use `begin` and - * `end` if you want to iterate the view in the expected order. - * - * @tparam Comp Type of component in which one is interested. - * @return A pointer to the array of components. + * @brief Estimates the number of entities iterated by the view. + * @return Estimated number of entities iterated by the view. */ - template - Comp * raw() const ENTT_NOEXCEPT { - return std::get *>(pools)->raw(); + [[nodiscard]] size_type size_hint() const ENTT_NOEXCEPT { + return view->size(); } /** - * @brief Direct access to the list of entities of a given pool. - * - * The returned pointer is such that range - * `[data(), data() + size()]` is always a valid range, - * even if the container is empty. + * @brief Returns an iterator to the first entity of the view. * - * @note - * There are no guarantees on the order of the entities. Use `begin` and - * `end` if you want to iterate the view in the expected order. + * The returned iterator points to the first entity of the view. If the view + * is empty, the returned iterator will be equal to `end()`. * - * @tparam Comp Type of component in which one is interested. - * @return A pointer to the array of entities. + * @return An iterator to the first entity of the view. */ - template - const entity_type * data() const ENTT_NOEXCEPT { - return std::get *>(pools)->data(); + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return iterator{view->begin(), view->end(), pools_to_array(std::index_sequence_for{}), filter}; } /** - * @brief Returns an iterator to the first entity that has the given - * components. + * @brief Returns an iterator that is past the last entity of the view. * - * The returned iterator points to the first entity that has the given - * components. If the view is empty, the returned iterator will be equal to - * `end()`. + * The returned iterator points to the entity following the last entity of + * the view. Attempting to dereference the returned iterator results in + * undefined behavior. * - * @note - * Input iterators stay true to the order imposed to the underlying data - * structures. - * - * @return An iterator to the first entity that has the given components. + * @return An iterator to the entity following the last entity of the view. */ - iterator_type begin() const ENTT_NOEXCEPT { - const auto *view = candidate(); - return iterator_type{unchecked(view), view->begin(), view->end()}; + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return iterator{view->end(), view->end(), pools_to_array(std::index_sequence_for{}), filter}; } /** - * @brief Returns an iterator that is past the last entity that has the - * given components. - * - * The returned iterator points to the entity following the last entity that - * has the given components. Attempting to dereference the returned iterator - * results in undefined behavior. - * - * @note - * Input iterators stay true to the order imposed to the underlying data - * structures. - * - * @return An iterator to the entity following the last entity that has the - * given components. + * @brief Returns the first entity of the view, if any. + * @return The first entity of the view if one exists, the null entity + * otherwise. */ - iterator_type end() const ENTT_NOEXCEPT { - const auto *view = candidate(); - return iterator_type{unchecked(view), view->end(), view->end()}; + [[nodiscard]] entity_type front() const ENTT_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 ENTT_NOEXCEPT { + auto it = view->rbegin(); + for(const auto last = view->rend(); it != last && !contains(*it); ++it) {} + return it == view->rend() ? null : *it; } /** * @brief Finds an entity. - * @param entt A valid entity identifier. + * @param entt A valid identifier. * @return An iterator to the given entity if it's found, past the end * iterator otherwise. */ - iterator_type find(const entity_type entt) const ENTT_NOEXCEPT { - const auto *view = candidate(); - iterator_type it{unchecked(view), view->find(entt), view->end()}; - return (it != end() && *it == entt) ? it : end(); + [[nodiscard]] iterator find(const entity_type entt) const ENTT_NOEXCEPT { + return contains(entt) ? iterator{view->find(entt), view->end(), pools_to_array(std::index_sequence_for{}), filter} : end(); + } + + /** + * @brief Returns the components assigned to the given entity. + * @param entt A valid identifier. + * @return The components assigned to the given entity. + */ + [[nodiscard]] decltype(auto) operator[](const entity_type entt) const { + return get(entt); + } + + /** + * @brief Checks if a view is properly initialized. + * @return True if the view is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return view != nullptr; } /** * @brief Checks if a view contains an entity. - * @param entt A valid entity identifier. + * @param entt A valid identifier. * @return True if the view contains the given entity, false otherwise. */ - bool contains(const entity_type entt) const ENTT_NOEXCEPT { - return find(entt) != end(); + [[nodiscard]] bool contains(const entity_type entt) const ENTT_NOEXCEPT { + return std::apply([entt](const auto *...curr) { return (curr->contains(entt) && ...); }, pools) + && std::apply([entt](const auto *...curr) { return (!curr->contains(entt) && ...); }, filter); } /** * @brief Returns the components assigned to the given entity. * - * Prefer this function instead of `registry::get` during iterations. It has - * far better performance than its companion function. - * * @warning - * Attempting to use an invalid component type results in a compilation - * error. Attempting to use an entity that doesn't belong to the view - * results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * view doesn't contain the given entity. + * Attempting to use an entity that doesn't belong to the view results in + * undefined behavior. * * @tparam Comp Types of components to get. - * @param entt A valid entity identifier. + * @param entt A valid identifier. * @return The components assigned to the entity. */ template - decltype(auto) get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT { - ENTT_ASSERT(contains(entt)); + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + ENTT_ASSERT(contains(entt), "View does not contain entity"); - if constexpr(sizeof...(Comp) == 1) { - return (std::get *>(pools)->get(entt), ...); + if constexpr(sizeof...(Comp) == 0) { + return std::apply([entt](auto *...curr) { return std::tuple_cat(curr->get_as_tuple(entt)...); }, pools); + } else if constexpr(sizeof...(Comp) == 1) { + return (std::get *>(pools)->get(entt), ...); } else { - return std::tuple(entt))...>{get(entt)...}; + return std::tuple_cat(std::get *>(pools)->get_as_tuple(entt)...); } } /** - * @brief Iterates entities and components 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 all its components. The - * _constness_ of the components is as requested.
- * The signature of the function must be equivalent to one of the following - * forms: - * - * @code{.cpp} - * void(const entity_type, Component &...); - * void(Component &...); - * @endcode + * @brief Returns the components assigned to the given entity. * - * @note - * Empty types aren't explicitly instantiated. Therefore, temporary objects - * are returned during iterations. They can be caught only by copy or with - * const references. + * @warning + * Attempting to use an entity that doesn't belong to the view results in + * undefined behavior. * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. + * @tparam First Index of a component to get. + * @tparam Other Indexes of other components to get. + * @param entt A valid identifier. + * @return The components assigned to the entity. */ - template - inline void each(Func func) const { - const auto *view = candidate(); - ((std::get *>(pools) == view ? each(std::move(func)) : void()), ...); - } + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + ENTT_ASSERT(contains(entt), "View does not contain entity"); - /** - * @brief Iterates entities and components 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 all its components. The - * _constness_ of the components is as requested.
- * The signature of the function must be equivalent to one of the following - * forms: - * - * @code{.cpp} - * void(const entity_type, Component &...); - * void(Component &...); - * @endcode - * - * The pool of the suggested component is used to lead the iterations. The - * returned entities will therefore respect the order of the pool associated - * with that type.
- * It is no longer guaranteed that the performance is the best possible, but - * there will be greater control over the order of iteration. - * - * @note - * Empty types aren't explicitly instantiated. Therefore, temporary objects - * are returned during iterations. They can be caught only by copy or with - * const references. - * - * @tparam Comp Type of component to use to enforce the iteration order. - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. - */ - template - inline void each(Func func) const { - using other_type = type_list_cat_t, type_list<>, type_list>...>; - traverse(std::move(func), other_type{}, type_list{}); + if constexpr(sizeof...(Other) == 0) { + return std::get(pools)->get(entt); + } else { + return std::tuple_cat(std::get(pools)->get_as_tuple(entt), std::get(pools)->get_as_tuple(entt)...); + } } /** @@ -451,65 +462,56 @@ public: * void(Type &...); * @endcode * - * @sa each - * * @tparam Func Type of the function object to invoke. * @param func A valid function object. */ template - inline void less(Func func) const { - const auto *view = candidate(); - ((std::get *>(pools) == view ? less(std::move(func)) : void()), ...); + void each(Func func) const { + pick_and_each(std::move(func), std::index_sequence_for{}); } /** - * @brief Iterates entities and components 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 components. The - * _constness_ of the components is as requested.
- * The signature of the function must be equivalent to one of the following - * forms: + * @brief Returns an iterable object to use to _visit_ a view. * - * @code{.cpp} - * void(const entity_type, Type &...); - * void(Type &...); - * @endcode - * - * The pool of the suggested component is used to lead the iterations. The - * returned entities will therefore respect the order of the pool associated - * with that type.
- * It is no longer guaranteed that the performance is the best possible, but - * there will be greater control over the order of iteration. + * The iterable object returns a tuple that contains the current entity and + * a set of references to its non-empty components. The _constness_ of the + * components is as requested. * - * @sa each - * - * @tparam Comp Type of component to use to enforce the iteration order. - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. + * @return An iterable object to use to _visit_ the view. */ - template - inline void less(Func func) const { - using other_type = type_list_cat_t, type_list<>, type_list>...>; - using non_empty_type = type_list_cat_t, type_list<>, type_list>...>; - traverse(std::move(func), other_type{}, non_empty_type{}); + [[nodiscard]] iterable each() const ENTT_NOEXCEPT { + return {internal::extended_view_iterator{begin(), pools}, internal::extended_view_iterator{end(), pools}}; + } + + /** + * @brief Combines two views in a _more specific_ one (friend function). + * @tparam Get Component list of the view to combine with. + * @tparam Excl Filter list of the view to combine with. + * @param other The view to combine with. + * @return A more specific view. + */ + template + [[nodiscard]] auto operator|(const basic_view, exclude_t> &other) const ENTT_NOEXCEPT { + using view_type = basic_view, exclude_t>; + return std::make_from_tuple(std::tuple_cat( + std::apply([](auto *...curr) { return std::forward_as_tuple(*curr...); }, pools), + std::apply([](auto *...curr) { return std::forward_as_tuple(*curr...); }, other.pools), + std::apply([](const auto *...curr) { return std::forward_as_tuple(static_cast &>(*curr)...); }, filter), + std::apply([](const auto *...curr) { return std::forward_as_tuple(static_cast &>(*curr)...); }, other.filter))); } private: - const std::tuple *...> pools; + std::tuple *...> pools; + std::array filter; + const base_type *view; }; - /** * @brief Single component view specialization. * * Single component views are specialized in order to get a boost in terms of * performance. This kind of views can access the underlying data structure - * directly and avoid superfluous checks.
- * Order of elements during iterations are highly dependent on the order of the - * underlying data structure. See sparse_set and its specializations for more - * details. + * directly and avoid superfluous checks. * * @b Important * @@ -518,137 +520,173 @@ private: * * New instances of the given component are created and assigned to entities. * * The entity currently pointed is modified (as an example, the given * component is removed from the entity to which the iterator points). + * * The entity currently pointed is destroyed. * - * In all the other cases, modifying the pool of the given component in any way + * In all other cases, modifying the pool iterated by the view in any way * invalidates all the iterators and using them results in undefined behavior. * - * @note - * Views share a reference to the underlying data structure of the registry that - * generated them. Therefore any change to the entities and to the components - * made by means of the registry are immediately reflected by views. - * - * @warning - * Lifetime of a view must overcome the one of the registry that generated it. - * In any other case, attempting to use a view results in undefined behavior. - * * @tparam Entity A valid entity type (see entt_traits for more details). * @tparam Component Type of component iterated by the view. */ template -class basic_view { - /*! @brief A registry is allowed to create views. */ - friend class basic_registry; - - using pool_type = std::conditional_t, const storage>, storage>; +class basic_view, exclude_t<>, std::void_t>::in_place_delete>>> { + template + friend class basic_view; - basic_view(pool_type *ref) ENTT_NOEXCEPT - : pool{ref} - {} + using storage_type = constness_as_t>::storage_type, Component>; public: - /*! @brief Type of component iterated by the view. */ - using raw_type = Component; /*! @brief Underlying entity identifier. */ - using entity_type = typename pool_type::entity_type; + using entity_type = Entity; /*! @brief Unsigned integer type. */ - using size_type = typename pool_type::size_type; - /*! @brief Input iterator type. */ - using iterator_type = typename sparse_set::iterator_type; + using size_type = std::size_t; + /*! @brief Common type among all storage types. */ + using base_type = typename storage_type::base_type; + /*! @brief Random access iterator type. */ + using iterator = typename base_type::iterator; + /*! @brief Reversed iterator type. */ + using reverse_iterator = typename base_type::reverse_iterator; + /*! @brief Iterable view type. */ + using iterable = decltype(std::declval().each()); + + /*! @brief Default constructor to use to create empty, invalid views. */ + basic_view() ENTT_NOEXCEPT + : pools{}, + filter{}, + view{} {} + + /** + * @brief Constructs a single-type view from a storage class. + * @param ref The storage for the type to iterate. + */ + basic_view(storage_type &ref) ENTT_NOEXCEPT + : pools{&ref}, + filter{}, + view{&ref} {} + + /** + * @brief Returns the leading storage of a view. + * @return The leading storage of the view. + */ + const base_type &handle() const ENTT_NOEXCEPT { + return *view; + } + + /** + * @brief Returns the storage for a given component type. + * @tparam Comp Type of component of which to return the storage. + * @return The storage for the given component type. + */ + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + static_assert(std::is_same_v, "Invalid component type"); + return *std::get<0>(pools); + } + + /** + * @brief Returns the storage for a given component type. + * @tparam Comp Index of component of which to return the storage. + * @return The storage for the given component type. + */ + template + [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT { + return *std::get(pools); + } /** * @brief Returns the number of entities that have the given component. * @return Number of entities that have the given component. */ - size_type size() const ENTT_NOEXCEPT { - return pool->size(); + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return view->size(); } /** - * @brief Checks whether the view is empty. + * @brief Checks whether a view is empty. * @return True if the view is empty, false otherwise. */ - bool empty() const ENTT_NOEXCEPT { - return pool->empty(); + [[nodiscard]] bool empty() const ENTT_NOEXCEPT { + return view->empty(); } /** - * @brief Direct access to the list of components. + * @brief Returns an iterator to the first entity of the view. * - * The returned pointer is such that range `[raw(), raw() + size()]` is - * always a valid range, even if the container is empty. + * The returned iterator points to the first entity of the view. If the view + * is empty, the returned iterator will be equal to `end()`. * - * @note - * There are no guarantees on the order of the components. Use `begin` and - * `end` if you want to iterate the view in the expected order. - * - * @return A pointer to the array of components. + * @return An iterator to the first entity of the view. */ - raw_type * raw() const ENTT_NOEXCEPT { - return pool->raw(); + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return view->begin(); } /** - * @brief Direct access to the list of entities. - * - * The returned pointer is such that range `[data(), data() + size()]` is - * always a valid range, even if the container is empty. + * @brief Returns an iterator that is past the last entity of the view. * - * @note - * There are no guarantees on the order of the entities. Use `begin` and - * `end` if you want to iterate the view in the expected order. + * The returned iterator points to the entity following the last entity of + * the view. Attempting to dereference the returned iterator results in + * undefined behavior. * - * @return A pointer to the array of entities. + * @return An iterator to the entity following the last entity of the view. */ - const entity_type * data() const ENTT_NOEXCEPT { - return pool->data(); + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return view->end(); } /** - * @brief Returns an iterator to the first entity that has the given - * component. + * @brief Returns an iterator to the first entity of the reversed view. * - * The returned iterator points to the first entity that has the given - * component. If the view is empty, the returned iterator will be equal to - * `end()`. + * The returned iterator points to the first entity of the reversed view. If + * the view is empty, the returned iterator will be equal to `rend()`. * - * @note - * Input iterators stay true to the order imposed to the underlying data - * structures. - * - * @return An iterator to the first entity that has the given component. + * @return An iterator to the first entity of the reversed view. */ - iterator_type begin() const ENTT_NOEXCEPT { - return pool->sparse_set::begin(); + [[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT { + return view->rbegin(); } /** - * @brief Returns an iterator that is past the last entity that has the - * given component. + * @brief Returns an iterator that is past the last entity of the reversed + * view. * - * The returned iterator points to the entity following the last entity that - * has the given component. Attempting to dereference the returned iterator + * The returned iterator points to the entity following the last entity of + * the reversed view. Attempting to dereference the returned iterator * results in undefined behavior. * - * @note - * Input iterators stay true to the order imposed to the underlying data - * structures. - * - * @return An iterator to the entity following the last entity that has the - * given component. + * @return An iterator to the entity following the last entity of the + * reversed view. + */ + [[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT { + return view->rend(); + } + + /** + * @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 ENTT_NOEXCEPT { + return empty() ? null : *begin(); + } + + /** + * @brief Returns the last entity of the view, if any. + * @return The last entity of the view if one exists, the null entity + * otherwise. */ - iterator_type end() const ENTT_NOEXCEPT { - return pool->sparse_set::end(); + [[nodiscard]] entity_type back() const ENTT_NOEXCEPT { + return empty() ? null : *rbegin(); } /** * @brief Finds an entity. - * @param entt A valid entity identifier. + * @param entt A valid identifier. * @return An iterator to the given entity if it's found, past the end * iterator otherwise. */ - iterator_type find(const entity_type entt) const ENTT_NOEXCEPT { - const auto it = pool->find(entt); - return it != end() && *it == entt ? it : end(); + [[nodiscard]] iterator find(const entity_type entt) const ENTT_NOEXCEPT { + return contains(entt) ? view->find(entt) : end(); } /** @@ -656,37 +694,64 @@ public: * @param pos Position of the element to return. * @return The identifier that occupies the given position. */ - entity_type operator[](const size_type pos) const ENTT_NOEXCEPT { + [[nodiscard]] entity_type operator[](const size_type pos) const { return begin()[pos]; } + /** + * @brief Returns the component assigned to the given entity. + * @param entt A valid identifier. + * @return The component assigned to the given entity. + */ + [[nodiscard]] decltype(auto) operator[](const entity_type entt) const { + return get(entt); + } + + /** + * @brief Checks if a view is properly initialized. + * @return True if the view is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return view != nullptr; + } + /** * @brief Checks if a view contains an entity. - * @param entt A valid entity identifier. + * @param entt A valid identifier. * @return True if the view contains the given entity, false otherwise. */ - bool contains(const entity_type entt) const ENTT_NOEXCEPT { - return find(entt) != end(); + [[nodiscard]] bool contains(const entity_type entt) const ENTT_NOEXCEPT { + return view->contains(entt); } /** * @brief Returns the component assigned to the given entity. * - * Prefer this function instead of `registry::get` during iterations. It has - * far better performance than its companion function. - * * @warning * Attempting to use an entity that doesn't belong to the view results in - * undefined behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * view doesn't contain the given entity. + * undefined behavior. * - * @param entt A valid entity identifier. + * @tparam Comp Type or index of the component to get. + * @param entt A valid identifier. * @return The component assigned to the entity. */ - decltype(auto) get(const entity_type entt) const ENTT_NOEXCEPT { - ENTT_ASSERT(contains(entt)); - return pool->get(entt); + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + ENTT_ASSERT(contains(entt), "View does not contain entity"); + + if constexpr(sizeof...(Comp) == 0) { + return std::get<0>(pools)->get_as_tuple(entt); + } else { + static_assert(std::is_same_v, "Invalid component type"); + return std::get<0>(pools)->get(entt); + } + } + + /*! @copydoc get */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + ENTT_ASSERT(contains(entt), "View does not contain entity"); + return std::get<0>(pools)->get(entt); } /** @@ -694,8 +759,8 @@ public: * object to them. * * The function object is invoked for each entity. It is provided with the - * entity itself and a reference to its component. The _constness_ of the - * component is as requested.
+ * entity itself and a reference to the component if it's a non-empty one. + * The _constness_ of the component is as requested.
* The signature of the function must be equivalent to one of the following * forms: * @@ -704,74 +769,73 @@ public: * void(Component &); * @endcode * - * @note - * Empty types aren't explicitly instantiated. Therefore, temporary objects - * are returned during iterations. They can be caught only by copy or with - * const references. - * * @tparam Func Type of the function object to invoke. * @param func A valid function object. */ template - inline void each(Func func) const { - if constexpr(std::is_invocable_v) { - std::for_each(pool->begin(), pool->end(), std::move(func)); + void each(Func func) const { + if constexpr(is_applicable_v) { + for(const auto pack: each()) { + std::apply(func, pack); + } + } else if constexpr(std::is_invocable_v) { + for(auto &&component: *std::get<0>(pools)) { + func(component); + } + } else if constexpr(std::is_invocable_v) { + for(auto entity: *view) { + func(entity); + } } else { - std::for_each(pool->sparse_set::begin(), pool->sparse_set::end(), [&func, raw = pool->begin()](const auto entt) mutable { - func(entt, *(raw++)); - }); + for(size_type pos{}, last = size(); pos < last; ++pos) { + func(); + } } } /** - * @brief Iterates entities and components 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 reference to its component if it's a non-empty one. - * The _constness_ of the component is as requested.
- * The signature of the function must be equivalent to one of the following - * forms in case the component isn't an empty one: + * @brief Returns an iterable object to use to _visit_ a view. * - * @code{.cpp} - * void(const entity_type, Component &); - * void(Component &); - * @endcode - * - * In case the component is an empty one instead, the following forms are - * accepted: + * The iterable object returns a tuple that contains the current entity and + * a reference to its component if it's a non-empty one. The _constness_ of + * the component is as requested. * - * @code{.cpp} - * void(const entity_type); - * void(); - * @endcode - * - * @sa each - * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. + * @return An iterable object to use to _visit_ the view. */ - template - inline void less(Func func) const { - if constexpr(std::is_empty_v) { - if constexpr(std::is_invocable_v) { - for(auto pos = pool->size(); pos; --pos) { - func(); - } - } else { - std::for_each(pool->sparse_set::begin(), pool->sparse_set::end(), std::move(func)); - } - } else { - each(std::move(func)); - } + [[nodiscard]] iterable each() const ENTT_NOEXCEPT { + return std::get<0>(pools)->each(); + } + + /** + * @brief Combines two views in a _more specific_ one (friend function). + * @tparam Get Component list of the view to combine with. + * @tparam Excl Filter list of the view to combine with. + * @param other The view to combine with. + * @return A more specific view. + */ + template + [[nodiscard]] auto operator|(const basic_view, exclude_t> &other) const ENTT_NOEXCEPT { + using view_type = basic_view, exclude_t>; + return std::make_from_tuple(std::tuple_cat( + std::forward_as_tuple(*std::get<0>(pools)), + std::apply([](auto *...curr) { return std::forward_as_tuple(*curr...); }, other.pools), + std::apply([](const auto *...curr) { return std::forward_as_tuple(static_cast &>(*curr)...); }, other.filter))); } private: - pool_type *pool; + std::tuple pools; + std::array filter; + const base_type *view; }; +/** + * @brief Deduction guide. + * @tparam Storage Type of storage classes used to create the view. + * @param storage The storage for the types to iterate. + */ +template +basic_view(Storage &...storage) -> basic_view, get_t...>, exclude_t<>>; -} - +} // namespace entt -#endif // ENTT_ENTITY_VIEW_HPP +#endif diff --git a/modules/entt/src/entt/entt.hpp b/modules/entt/src/entt/entt.hpp index 2712c32..8ab5bf9 100644 --- a/modules/entt/src/entt/entt.hpp +++ b/modules/entt/src/entt/entt.hpp @@ -1,29 +1,59 @@ +#include "config/config.h" +#include "config/macro.h" +#include "config/version.h" +#include "container/dense_map.hpp" +#include "container/dense_set.hpp" #include "core/algorithm.hpp" +#include "core/any.hpp" +#include "core/attribute.h" +#include "core/compressed_pair.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/tuple.hpp" +#include "core/type_info.hpp" #include "core/type_traits.hpp" #include "core/utility.hpp" -#include "entity/actor.hpp" +#include "entity/component.hpp" #include "entity/entity.hpp" #include "entity/group.hpp" +#include "entity/handle.hpp" #include "entity/helper.hpp" -#include "entity/prototype.hpp" +#include "entity/observer.hpp" +#include "entity/organizer.hpp" #include "entity/registry.hpp" #include "entity/runtime_view.hpp" +#include "entity/sigh_storage_mixin.hpp" #include "entity/snapshot.hpp" #include "entity/sparse_set.hpp" #include "entity/storage.hpp" +#include "entity/utility.hpp" #include "entity/view.hpp" #include "locator/locator.hpp" +#include "meta/adl_pointer.hpp" +#include "meta/container.hpp" +#include "meta/ctx.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 "platform/android-ndk-r17.hpp" +#include "poly/poly.hpp" #include "process/process.hpp" #include "process/scheduler.hpp" #include "resource/cache.hpp" -#include "resource/handle.hpp" #include "resource/loader.hpp" +#include "resource/resource.hpp" #include "signal/delegate.hpp" #include "signal/dispatcher.hpp" #include "signal/emitter.hpp" diff --git a/modules/entt/src/entt/fwd.hpp b/modules/entt/src/entt/fwd.hpp index df1389e..e4ebc31 100644 --- a/modules/entt/src/entt/fwd.hpp +++ b/modules/entt/src/entt/fwd.hpp @@ -1,3 +1,7 @@ +#include "container/fwd.hpp" +#include "core/fwd.hpp" #include "entity/fwd.hpp" +#include "meta/fwd.hpp" +#include "poly/fwd.hpp" #include "resource/fwd.hpp" #include "signal/fwd.hpp" diff --git a/modules/entt/src/entt/locator/locator.hpp b/modules/entt/src/entt/locator/locator.hpp index 8d529f9..7387aa0 100644 --- a/modules/entt/src/entt/locator/locator.hpp +++ b/modules/entt/src/entt/locator/locator.hpp @@ -1,111 +1,114 @@ #ifndef ENTT_LOCATOR_LOCATOR_HPP #define ENTT_LOCATOR_LOCATOR_HPP - #include #include #include "../config/config.h" - namespace entt { - /** * @brief Service locator, nothing more. * - * A service locator can be used to do what it promises: locate services.
+ * 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 template - * based implementation tries to fill the gap and to get rid of the burden of - * defining a different specific locator for each application. + * 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. * - * @tparam Service Type of service managed by the locator. + * @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 -struct service_locator { - /*! @brief Type of service offered. */ - using service_type = Service; +struct locator final { + /*! @brief Service type. */ + using type = Service; /*! @brief Default constructor, deleted on purpose. */ - service_locator() = delete; + locator() = delete; /*! @brief Default destructor, deleted on purpose. */ - ~service_locator() = delete; + ~locator() = delete; /** - * @brief Tests if a valid service implementation is set. - * @return True if the service is set, false otherwise. + * @brief Checks whether a service locator contains a value. + * @return True if the service locator contains a value, false otherwise. */ - inline static bool empty() ENTT_NOEXCEPT { - return !static_cast(service); + [[nodiscard]] static bool has_value() ENTT_NOEXCEPT { + return (service != nullptr); } /** - * @brief Returns a weak pointer to a service implementation, if any. + * @brief Returns a reference to a valid service, if any. * - * Clients of a service shouldn't retain references to it. The recommended - * way is to retrieve the service implementation currently set each and - * every time the need of using it arises. Otherwise users can incur in - * unexpected behaviors. + * @warning + * Invoking this function can result in undefined behavior if the service + * hasn't been set yet. * - * @return A reference to the service implementation currently set, if any. + * @return A reference to the service currently set, if any. */ - inline static std::weak_ptr get() ENTT_NOEXCEPT { - return service; + [[nodiscard]] static Service &value() ENTT_NOEXCEPT { + ENTT_ASSERT(has_value(), "Service not available"); + return *service; } /** - * @brief Returns a weak reference to a service implementation, if any. - * - * Clients of a service shouldn't retain references to it. The recommended - * way is to retrieve the service implementation currently set each and - * every time the need of using it arises. Otherwise users can incur in - * unexpected behaviors. + * @brief Returns a service if available or sets it from a fallback type. * - * @warning - * In case no service implementation has been set, a call to this function - * results in undefined behavior. + * Arguments are used only if a service doesn't already exist. In all other + * cases, they are discarded. * - * @return A reference to the service implementation currently set, if any. + * @tparam Args Types of arguments to use to construct the fallback service. + * @tparam Impl Fallback service type. + * @param args Parameters to use to construct the fallback service. + * @return A reference to a valid service. */ - inline static Service & ref() ENTT_NOEXCEPT { - return *service; + template + [[nodiscard]] static Service &value_or(Args &&...args) { + return service ? *service : emplace(std::forward(args)...); } /** * @brief Sets or replaces a service. - * @tparam Impl Type of the new service to use. + * @tparam Impl 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 - inline static void set(Args &&... args) { + static Service &emplace(Args &&...args) { service = std::make_shared(std::forward(args)...); + return *service; } /** - * @brief Sets or replaces a service. - * @param ptr Service to use to replace the current one. + * @brief Sets or replaces a service using a given allocator. + * @tparam Impl Service type. + * @tparam Allocator Type of allocator used to manage memory and elements. + * @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. */ - inline static void set(std::shared_ptr ptr) { - ENTT_ASSERT(static_cast(ptr)); - service = std::move(ptr); + template + static Service &allocate_emplace(Allocator alloc, Args &&...args) { + service = std::allocate_shared(alloc, std::forward(args)...); + return *service; } - /** - * @brief Resets a service. - * - * The service is no longer valid after a reset. - */ - inline static void reset() { + /*! @brief Resets a service. */ + static void reset() ENTT_NOEXCEPT { service.reset(); } private: + // std::shared_ptr because of its type erased allocator which is pretty useful here inline static std::shared_ptr service = nullptr; }; +} // namespace entt -} - - -#endif // ENTT_LOCATOR_LOCATOR_HPP +#endif diff --git a/modules/entt/src/entt/meta/adl_pointer.hpp b/modules/entt/src/entt/meta/adl_pointer.hpp new file mode 100644 index 0000000..5bb768a --- /dev/null +++ b/modules/entt/src/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/modules/entt/src/entt/meta/container.hpp b/modules/entt/src/entt/meta/container.hpp new file mode 100644 index 0000000..b62ab7b --- /dev/null +++ b/modules/entt/src/entt/meta/container.hpp @@ -0,0 +1,239 @@ +#ifndef ENTT_META_CONTAINER_HPP +#define ENTT_META_CONTAINER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include "../container/dense_map.hpp" +#include "../container/dense_set.hpp" +#include "meta.hpp" +#include "type_traits.hpp" + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct is_dynamic_sequence_container: std::false_type {}; + +template +struct is_dynamic_sequence_container>: std::true_type {}; + +template +struct is_key_only_meta_associative_container: std::true_type {}; + +template +struct is_key_only_meta_associative_container>: std::false_type {}; + +template +struct basic_meta_sequence_container_traits { + using iterator = meta_sequence_container::iterator; + using size_type = std::size_t; + + [[nodiscard]] static size_type size(const any &container) ENTT_NOEXCEPT { + return any_cast(container).size(); + } + + [[nodiscard]] static bool resize([[maybe_unused]] any &container, [[maybe_unused]] size_type sz) { + if constexpr(is_dynamic_sequence_container::value) { + if(auto *const cont = any_cast(&container); cont) { + cont->resize(sz); + return true; + } + } + + return false; + } + + [[nodiscard]] static iterator iter(any &container, const bool as_end) { + if(auto *const cont = any_cast(&container); cont) { + return iterator{*cont, static_cast(as_end * cont->size())}; + } + + const Type &as_const = any_cast(container); + return iterator{as_const, static_cast(as_end * as_const.size())}; + } + + [[nodiscard]] static iterator insert([[maybe_unused]] any &container, [[maybe_unused]] const std::ptrdiff_t offset, [[maybe_unused]] meta_any &value) { + if constexpr(is_dynamic_sequence_container::value) { + if(auto *const cont = any_cast(&container); cont) { + // this abomination is necessary because only on macos value_type and const_reference are different types for std::vector + if(value.allow_cast() || value.allow_cast()) { + const auto *element = value.try_cast>(); + const auto curr = cont->insert(cont->begin() + offset, element ? *element : value.cast()); + return iterator{*cont, curr - cont->begin()}; + } + } + } + + return {}; + } + + [[nodiscard]] static iterator erase([[maybe_unused]] any &container, [[maybe_unused]] const std::ptrdiff_t offset) { + if constexpr(is_dynamic_sequence_container::value) { + if(auto *const cont = any_cast(&container); cont) { + const auto curr = cont->erase(cont->begin() + offset); + return iterator{*cont, curr - cont->begin()}; + } + } + + return {}; + } +}; + +template +struct basic_meta_associative_container_traits { + using iterator = meta_associative_container::iterator; + using size_type = std::size_t; + + static constexpr auto key_only = is_key_only_meta_associative_container::value; + + [[nodiscard]] static size_type size(const any &container) ENTT_NOEXCEPT { + return any_cast(container).size(); + } + + [[nodiscard]] static bool clear(any &container) { + if(auto *const cont = any_cast(&container); cont) { + cont->clear(); + return true; + } + + return false; + } + + [[nodiscard]] static iterator iter(any &container, const bool as_end) { + if(auto *const cont = any_cast(&container); cont) { + return iterator{std::integral_constant{}, as_end ? cont->end() : cont->begin()}; + } + + const auto &as_const = any_cast(container); + return iterator{std::integral_constant{}, as_end ? as_const.end() : as_const.begin()}; + } + + [[nodiscard]] static bool insert(any &container, meta_any &key, [[maybe_unused]] meta_any &value) { + auto *const cont = any_cast(&container); + + if constexpr(is_key_only_meta_associative_container::value) { + return cont && key.allow_cast() + && cont->insert(key.cast()).second; + } else { + return cont && key.allow_cast() && value.allow_cast() + && cont->emplace(key.cast(), value.cast()).second; + } + } + + [[nodiscard]] static bool erase(any &container, meta_any &key) { + auto *const cont = any_cast(&container); + return cont && key.allow_cast() + && (cont->erase(key.cast()) != cont->size()); + } + + [[nodiscard]] static iterator find(any &container, meta_any &key) { + if(key.allow_cast()) { + if(auto *const cont = any_cast(&container); cont) { + return iterator{std::integral_constant{}, cont->find(key.cast())}; + } + + return iterator{std::integral_constant{}, any_cast(container).find(key.cast())}; + } + + return {}; + } +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Meta sequence container traits for `std::vector`s of any type. + * @tparam Type The type of elements. + * @tparam Args Other arguments. + */ +template +struct meta_sequence_container_traits> + : internal::basic_meta_sequence_container_traits> {}; + +/** + * @brief Meta sequence container traits for `std::array`s of any type. + * @tparam Type The type of elements. + * @tparam N The number of elements. + */ +template +struct meta_sequence_container_traits> + : internal::basic_meta_sequence_container_traits> {}; + +/** + * @brief Meta associative container traits for `std::map`s of any type. + * @tparam Key The key type of elements. + * @tparam Value The value type of elements. + * @tparam Args Other arguments. + */ +template +struct meta_associative_container_traits> + : internal::basic_meta_associative_container_traits> {}; + +/** + * @brief Meta associative container traits for `std::unordered_map`s of any + * type. + * @tparam Key The key type of elements. + * @tparam Value The value type of elements. + * @tparam Args Other arguments. + */ +template +struct meta_associative_container_traits> + : internal::basic_meta_associative_container_traits> {}; + +/** + * @brief Meta associative container traits for `std::set`s of any type. + * @tparam Key The type of elements. + * @tparam Args Other arguments. + */ +template +struct meta_associative_container_traits> + : internal::basic_meta_associative_container_traits> {}; + +/** + * @brief Meta associative container traits for `std::unordered_set`s of any + * type. + * @tparam Key The type of elements. + * @tparam Args Other arguments. + */ +template +struct meta_associative_container_traits> + : internal::basic_meta_associative_container_traits> {}; + +/** + * @brief Meta associative container traits for `dense_map`s of any type. + * @tparam Key The key type of the elements. + * @tparam Type The value type of the elements. + * @tparam Args Other arguments. + */ +template +struct meta_associative_container_traits> + : internal::basic_meta_associative_container_traits> {}; + +/** + * @brief Meta associative container traits for `dense_set`s of any type. + * @tparam Type The value type of the elements. + * @tparam Args Other arguments. + */ +template +struct meta_associative_container_traits> + : internal::basic_meta_associative_container_traits> {}; + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/meta/ctx.hpp b/modules/entt/src/entt/meta/ctx.hpp new file mode 100644 index 0000000..7b20c4b --- /dev/null +++ b/modules/entt/src/entt/meta/ctx.hpp @@ -0,0 +1,57 @@ +#ifndef ENTT_META_CTX_HPP +#define ENTT_META_CTX_HPP + +#include "../config/config.h" +#include "../core/attribute.h" + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +struct meta_type_node; + +struct ENTT_API meta_context { + // we could use the lines below but VS2017 returns with an ICE if combined with ENTT_API despite the code being valid C++ + // inline static meta_type_node *local = nullptr; + // inline static meta_type_node **global = &local; + + [[nodiscard]] static meta_type_node *&local() ENTT_NOEXCEPT { + static meta_type_node *chain = nullptr; + return chain; + } + + [[nodiscard]] static meta_type_node **&global() ENTT_NOEXCEPT { + static meta_type_node **chain = &local(); + return chain; + } +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/*! @brief Opaque container for a meta context. */ +struct meta_ctx { + /** + * @brief Binds the meta system to a given context. + * @param other A valid context to which to bind. + */ + static void bind(meta_ctx other) ENTT_NOEXCEPT { + internal::meta_context::global() = other.ctx; + } + +private: + internal::meta_type_node **ctx{&internal::meta_context::local()}; +}; + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/meta/factory.hpp b/modules/entt/src/entt/meta/factory.hpp index bd82d3b..66b88ff 100644 --- a/modules/entt/src/entt/meta/factory.hpp +++ b/modules/entt/src/entt/meta/factory.hpp @@ -1,196 +1,230 @@ #ifndef ENTT_META_FACTORY_HPP #define ENTT_META_FACTORY_HPP - -#include #include +#include +#include +#include #include +#include #include "../config/config.h" -#include "../core/hashed_string.hpp" +#include "../core/fwd.hpp" +#include "../core/type_info.hpp" +#include "../core/type_traits.hpp" #include "meta.hpp" - +#include "node.hpp" +#include "policy.hpp" +#include "range.hpp" +#include "utility.hpp" namespace entt { - -template +/** + * @brief Meta factory to be used for reflection purposes. + * + * The meta factory is an utility class used to reflect types, data members and + * functions of all sorts. This class ensures that the underlying web of types + * is built correctly and performs some checks in debug mode to ensure that + * there are no subtle errors at runtime. + */ +template class meta_factory; - -template -meta_factory reflect(const char *str, Property &&... property) ENTT_NOEXCEPT; - - -template -bool unregister() ENTT_NOEXCEPT; - - /** - * @brief A meta factory to be used for reflection purposes. - * - * A meta factory is an utility class used to reflect types, data and functions - * of all sorts. This class ensures that the underlying web of types is built - * correctly and performs some checks in debug mode to ensure that there are no - * subtle errors at runtime. - * + * @brief Extended meta factory to be used for reflection purposes. * @tparam Type Reflected type for which the factory was created. + * @tparam Spec Property specialization pack used to disambiguate overloads. */ -template -class meta_factory { - static_assert(std::is_object_v && !(std::is_const_v || std::is_volatile_v)); +template +class meta_factory: public meta_factory { + void link_prop_if_required(internal::meta_prop_node &node) ENTT_NOEXCEPT { + if(meta_range range{*ref}; std::find(range.cbegin(), range.cend(), &node) == range.cend()) { + ENTT_ASSERT(std::find_if(range.cbegin(), range.cend(), [&node](const auto *curr) { return curr->id == node.id; }) == range.cend(), "Duplicate identifier"); + node.next = *ref; + *ref = &node; + } + } - template - inline bool duplicate(const hashed_string &name, const Node *node) ENTT_NOEXCEPT { - return node ? node->name == name || duplicate(name, node->next) : false; + template + void unroll(choice_t<2>, std::tuple property, Other &&...other) ENTT_NOEXCEPT { + std::apply([this](auto &&...curr) { (this->unroll(choice<2>, std::forward(curr)...)); }, property); + unroll(choice<2>, std::forward(other)...); } - inline bool duplicate(const meta_any &key, const internal::meta_prop_node *node) ENTT_NOEXCEPT { - return node ? node->key() == key || duplicate(key, node->next) : false; + template + void unroll(choice_t<1>, std::pair property, Other &&...other) ENTT_NOEXCEPT { + assign(std::move(property.first), std::move(property.second)); + unroll(choice<2>, std::forward(other)...); } - template - internal::meta_prop_node * properties() { - return nullptr; + template + void unroll(choice_t<0>, Property &&property, Other &&...other) ENTT_NOEXCEPT { + assign(std::forward(property)); + unroll(choice<2>, std::forward(other)...); } - template - internal::meta_prop_node * properties(Property &&property, Other &&... other) { - static std::remove_cv_t> prop{}; + template + void unroll(choice_t<0>) ENTT_NOEXCEPT {} + + template + void assign(meta_any key, meta_any value = {}) { + static meta_any property[2u]{}; static internal::meta_prop_node node{ nullptr, - []() -> meta_any { - return std::get<0>(prop); - }, - []() -> meta_any { - return std::get<1>(prop); - }, - []() -> meta_prop { - return &node; - } + property[0u], + property[1u] + // tricks clang-format }; - prop = std::forward(property); - node.next = properties(std::forward(other)...); - ENTT_ASSERT(!duplicate(meta_any{std::get<0>(prop)}, node.next)); - return &node; + property[0u] = std::move(key); + property[1u] = std::move(value); + + link_prop_if_required(node); } - template - meta_factory type(hashed_string name, Property &&... property) ENTT_NOEXCEPT { - static internal::meta_type_node node{ - {}, - nullptr, - nullptr, - std::is_void_v, - std::is_integral_v, - std::is_floating_point_v, - std::is_array_v, - std::is_enum_v, - std::is_union_v, - std::is_class_v, - std::is_pointer_v, - std::is_function_v, - std::is_member_object_pointer_v, - std::is_member_function_pointer_v, - std::extent_v, - []() -> meta_type { - return internal::meta_info>::resolve(); - }, - &internal::destroy, - []() -> meta_type { - return &node; - } - }; +public: + /** + * @brief Constructs an extended factory from a given node. + * @param target The underlying node to which to assign the properties. + */ + meta_factory(internal::meta_prop_node **target) ENTT_NOEXCEPT + : ref{target} {} - node.name = name; - node.next = internal::meta_info<>::type; - node.prop = properties(std::forward(property)...); - ENTT_ASSERT(!duplicate(name, node.next)); - ENTT_ASSERT(!internal::meta_info::type); - internal::meta_info::type = &node; - internal::meta_info<>::type = &node; + /** + * @brief Assigns a property to the last meta object created. + * + * Both the key and the value (if any) must be at least copy constructible. + * + * @tparam PropertyOrKey Type of the property or property key. + * @tparam Value Optional type of the property value. + * @param property_or_key Property or property key. + * @param value Optional property value. + * @return A meta factory for the parent type. + */ + template + meta_factory prop(PropertyOrKey &&property_or_key, Value &&...value) { + if constexpr(sizeof...(Value) == 0) { + unroll(choice<2>, std::forward(property_or_key)); + } else { + assign(std::forward(property_or_key), std::forward(value)...); + } - return *this; + return {}; } - void unregister_prop(internal::meta_prop_node **prop) { - while(*prop) { - auto *node = *prop; - *prop = node->next; - node->next = nullptr; - } + /** + * @brief Assigns properties to the last meta object created. + * + * Both key and value (if any) must be at least copy constructible. + * + * @tparam Property Types of the properties. + * @param property Properties to assign to the last meta object created. + * @return A meta factory for the parent type. + */ + template + meta_factory props(Property... property) { + unroll(choice<2>, std::forward(property)...); + return {}; } - void unregister_dtor() { - if(auto node = internal::meta_info::type->dtor; node) { - internal::meta_info::type->dtor = nullptr; - *node->underlying = nullptr; +private: + internal::meta_prop_node **ref; +}; + +/** + * @brief Basic meta factory to be used for reflection purposes. + * @tparam Type Reflected type for which the factory was created. + */ +template +class meta_factory { + void link_base_if_required(internal::meta_base_node &node) ENTT_NOEXCEPT { + if(meta_range range{owner->base}; std::find(range.cbegin(), range.cend(), &node) == range.cend()) { + node.next = owner->base; + owner->base = &node; } } - template - auto unregister_all(int) - -> decltype((internal::meta_info::type->*Member)->prop, void()) { - while(internal::meta_info::type->*Member) { - auto node = internal::meta_info::type->*Member; - internal::meta_info::type->*Member = node->next; - unregister_prop(&node->prop); - node->next = nullptr; - *node->underlying = nullptr; + void link_conv_if_required(internal::meta_conv_node &node) ENTT_NOEXCEPT { + if(meta_range range{owner->conv}; std::find(range.cbegin(), range.cend(), &node) == range.cend()) { + node.next = owner->conv; + owner->conv = &node; } } - template - void unregister_all(char) { - while(internal::meta_info::type->*Member) { - auto node = internal::meta_info::type->*Member; - internal::meta_info::type->*Member = node->next; - node->next = nullptr; - *node->underlying = nullptr; + void link_ctor_if_required(internal::meta_ctor_node &node) ENTT_NOEXCEPT { + if(meta_range range{owner->ctor}; std::find(range.cbegin(), range.cend(), &node) == range.cend()) { + node.next = owner->ctor; + owner->ctor = &node; } } - bool unregister() ENTT_NOEXCEPT { - const auto registered = internal::meta_info::type; + void link_data_if_required(const id_type id, internal::meta_data_node &node) ENTT_NOEXCEPT { + meta_range range{owner->data}; + ENTT_ASSERT(std::find_if(range.cbegin(), range.cend(), [id, &node](const auto *curr) { return curr != &node && curr->id == id; }) == range.cend(), "Duplicate identifier"); + node.id = id; - if(registered) { - if(auto *curr = internal::meta_info<>::type; curr == internal::meta_info::type) { - internal::meta_info<>::type = internal::meta_info::type->next; - } else { - while(curr && curr->next != internal::meta_info::type) { - curr = curr->next; - } + if(std::find(range.cbegin(), range.cend(), &node) == range.cend()) { + node.next = owner->data; + owner->data = &node; + } + } - if(curr) { - curr->next = internal::meta_info::type->next; - } - } + void link_func_if_required(const id_type id, internal::meta_func_node &node) ENTT_NOEXCEPT { + node.id = id; - unregister_prop(&internal::meta_info::type->prop); - unregister_all<&internal::meta_type_node::base>(0); - unregister_all<&internal::meta_type_node::conv>(0); - unregister_all<&internal::meta_type_node::ctor>(0); - unregister_all<&internal::meta_type_node::data>(0); - unregister_all<&internal::meta_type_node::func>(0); - unregister_dtor(); - - internal::meta_info::type->name = {}; - internal::meta_info::type->next = nullptr; - internal::meta_info::type = nullptr; + if(meta_range range{owner->func}; std::find(range.cbegin(), range.cend(), &node) == range.cend()) { + node.next = owner->func; + owner->func = &node; } - - return registered; } - meta_factory() ENTT_NOEXCEPT = default; + template + auto data(const id_type id, std::index_sequence) ENTT_NOEXCEPT { + using data_type = std::invoke_result_t; + using args_type = type_list)>::args_type...>; + static_assert(Policy::template value, "Invalid return type for the given policy"); + + static internal::meta_data_node node{ + {}, + /* this is never static */ + (std::is_member_object_pointer_v)> && ... && std::is_const_v>) ? internal::meta_traits::is_const : internal::meta_traits::is_none, + nullptr, + nullptr, + Setter::size, + internal::meta_node>>::resolve(), + &meta_arg::size != 1u, type_list_element_t>...>>, + [](meta_handle instance, meta_any value) -> bool { return (meta_setter>(*instance.operator->(), value.as_ref()) || ...); }, + &meta_getter + // tricks clang-format + }; + + link_data_if_required(id, node); + return meta_factory>{&node.prop}; + } public: - template - friend meta_factory reflect(const char *str, Property &&... property) ENTT_NOEXCEPT; + /*! @brief Default constructor. */ + meta_factory() ENTT_NOEXCEPT + : owner{internal::meta_node::resolve()} {} + + /** + * @brief Makes a meta type _searchable_. + * @param id Optional unique identifier. + * @return An extended meta factory for the given type. + */ + auto type(const id_type id = type_hash::value()) ENTT_NOEXCEPT { + meta_range range{*internal::meta_context::global()}; + ENTT_ASSERT(std::find_if(range.cbegin(), range.cend(), [id, this](const auto *curr) { return curr != owner && curr->id == id; }) == range.cend(), "Duplicate identifier"); + owner->id = id; + + if(std::find(range.cbegin(), range.cend(), owner) == range.cend()) { + owner->next = *internal::meta_context::global(); + *internal::meta_context::global() = owner; + } - template - friend bool unregister() ENTT_NOEXCEPT; + return meta_factory{&owner->prop}; + } /** * @brief Assigns a meta base to a meta type. @@ -201,29 +235,51 @@ public: * @return A meta factory for the parent type. */ template - meta_factory base() ENTT_NOEXCEPT { - static_assert(std::is_base_of_v); - auto * const type = internal::meta_info::resolve(); + auto base() ENTT_NOEXCEPT { + static_assert(!std::is_same_v && std::is_base_of_v, "Invalid base type"); static internal::meta_base_node node{ - &internal::meta_info::template base, - type, nullptr, - &internal::meta_info::resolve, - [](void *instance) -> void * { - return static_cast(static_cast(instance)); - }, - []() -> meta_base { - return &node; + internal::meta_node::resolve(), + [](meta_any other) ENTT_NOEXCEPT -> meta_any { + if(auto *ptr = other.data(); ptr) { + return forward_as_meta(*static_cast(static_cast(ptr))); + } + + return forward_as_meta(*static_cast(static_cast(std::as_const(other).data()))); } + // tricks clang-format }; - node.next = type->base; - ENTT_ASSERT((!internal::meta_info::template base)); - internal::meta_info::template base = &node; - type->base = &node; + link_base_if_required(node); + return meta_factory{}; + } + + /** + * @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() ENTT_NOEXCEPT { + static internal::meta_conv_node node{ + nullptr, + internal::meta_node>>>::resolve(), + [](const meta_any &instance) -> meta_any { + return forward_as_meta(std::invoke(Candidate, *static_cast(instance.data()))); + } + // tricks clang-format + }; - return *this; + link_conv_if_required(node); + return meta_factory{}; } /** @@ -236,73 +292,47 @@ public: * @return A meta factory for the parent type. */ template - meta_factory conv() ENTT_NOEXCEPT { - static_assert(std::is_convertible_v); - auto * const type = internal::meta_info::resolve(); - + auto conv() ENTT_NOEXCEPT { static internal::meta_conv_node node{ - &internal::meta_info::template conv, - type, nullptr, - &internal::meta_info::resolve, - [](void *instance) -> meta_any { - return static_cast(*static_cast(instance)); - }, - []() -> meta_conv { - return &node; - } + internal::meta_node>>::resolve(), + [](const meta_any &instance) -> meta_any { return forward_as_meta(static_cast(*static_cast(instance.data()))); } + // tricks clang-format }; - node.next = type->conv; - ENTT_ASSERT((!internal::meta_info::template conv)); - internal::meta_info::template conv = &node; - type->conv = &node; - - return *this; + link_conv_if_required(node); + return meta_factory{}; } /** * @brief Assigns a meta constructor to a meta type. * - * Free functions 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.
+ * 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 a free function. + * type is a built-in one or not. * - * @tparam Func The actual function to use as a constructor. - * @tparam Property Types of properties to assign to the meta data. - * @param property Properties to assign to the meta data. - * @return A meta factory for the parent type. + * @tparam Candidate The actual function to use as a constructor. + * @tparam Policy Optional policy (no policy set by default). + * @return An extended meta factory for the parent type. */ - template - meta_factory ctor(Property &&... property) ENTT_NOEXCEPT { - using helper_type = internal::meta_function_helper>; - static_assert(std::is_same_v); - auto * const type = internal::meta_info::resolve(); + template + auto ctor() ENTT_NOEXCEPT { + using descriptor = meta_function_helper_t; + static_assert(Policy::template value, "Invalid return type for the given policy"); + static_assert(std::is_same_v>, Type>, "The function doesn't return an object of the required type"); static internal::meta_ctor_node node{ - &internal::meta_info::template ctor, - type, nullptr, - nullptr, - helper_type::size, - &helper_type::arg, - [](meta_any * const any) { - return internal::invoke(nullptr, any, std::make_index_sequence{}); - }, - []() -> meta_ctor { - return &node; - } + descriptor::args_type::size, + &meta_arg, + &meta_construct + // tricks clang-format }; - node.next = type->ctor; - node.prop = properties(std::forward(property)...); - ENTT_ASSERT((!internal::meta_info::template ctor)); - internal::meta_info::template ctor = &node; - type->ctor = &node; - - return *this; + link_ctor_if_required(node); + return meta_factory{}; } /** @@ -313,79 +343,47 @@ public: * type that can be invoked with parameters whose types are those given. * * @tparam Args Types of arguments to use to construct an instance. - * @tparam Property Types of properties to assign to the meta data. - * @param property Properties to assign to the meta data. - * @return A meta factory for the parent type. + * @return An extended meta factory for the parent type. */ - template - meta_factory ctor(Property &&... property) ENTT_NOEXCEPT { - using helper_type = internal::meta_function_helper; - auto * const type = internal::meta_info::resolve(); + template + auto ctor() ENTT_NOEXCEPT { + using descriptor = meta_function_helper_t; static internal::meta_ctor_node node{ - &internal::meta_info::template ctor, - type, - nullptr, nullptr, - helper_type::size, - &helper_type::arg, - [](meta_any * const any) { - return internal::construct(any, std::make_index_sequence{}); - }, - []() -> meta_ctor { - return &node; - } + descriptor::args_type::size, + &meta_arg, + &meta_construct + // tricks clang-format }; - node.next = type->ctor; - node.prop = properties(std::forward(property)...); - ENTT_ASSERT((!internal::meta_info::template ctor)); - internal::meta_info::template ctor = &node; - type->ctor = &node; - - return *this; + link_ctor_if_required(node); + return meta_factory{}; } /** * @brief Assigns a meta destructor to a meta type. * - * Free functions can be assigned to meta types in the role of destructors. - * The signature of the function should identical to the following: + * Both free functions and member functions can be assigned to meta types in + * the role of destructors.
+ * The signature of a free function should be identical to the following: * * @code{.cpp} - * void(Type *); + * void(Type &); * @endcode * - * From a client's point of view, nothing changes if the destructor of a - * meta type is the default one or a custom one. + * Member functions should not take arguments instead.
+ * The purpose is to give users the ability to free up resources that + * require special treatment before an object is actually destroyed. * * @tparam Func The actual function to use as a destructor. * @return A meta factory for the parent type. */ - template - meta_factory dtor() ENTT_NOEXCEPT { - static_assert(std::is_invocable_v); - auto * const type = internal::meta_info::resolve(); - - static internal::meta_dtor_node node{ - &internal::meta_info::template dtor, - type, - [](meta_handle handle) { - return handle.type() == internal::meta_info::resolve()->meta() - ? ((*Func)(static_cast(handle.data())), true) - : false; - }, - []() -> meta_dtor { - return &node; - } - }; - - ENTT_ASSERT(!internal::meta_info::type->dtor); - ENTT_ASSERT((!internal::meta_info::template dtor)); - internal::meta_info::template dtor = &node; - internal::meta_info::type->dtor = &node; - - return *this; + template + auto dtor() ENTT_NOEXCEPT { + static_assert(std::is_invocable_v, "The function doesn't accept an object of the type provided"); + owner->dtor = [](void *instance) { std::invoke(Func, *static_cast(instance)); }; + return meta_factory{}; } /** @@ -397,88 +395,50 @@ public: * 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 Property Types of properties to assign to the meta data. - * @param str The name to assign to the meta data. - * @param property Properties to assign to the meta data. - * @return A meta factory for the parent type. + * @tparam Policy Optional policy (no policy set by default). + * @param id Unique identifier. + * @return An extended meta factory for the parent type. */ - template - meta_factory data(const char *str, Property &&... property) ENTT_NOEXCEPT { - auto * const type = internal::meta_info::resolve(); - internal::meta_data_node *curr = nullptr; + template + auto data(const id_type id) ENTT_NOEXCEPT { + if constexpr(std::is_member_object_pointer_v) { + using data_type = std::remove_reference_t>; - if constexpr(std::is_same_v) { static internal::meta_data_node node{ - &internal::meta_info::template data, {}, - type, + /* this is never static */ + std::is_const_v ? internal::meta_traits::is_const : internal::meta_traits::is_none, nullptr, nullptr, - true, - true, - &internal::meta_info::resolve, - [](meta_handle, meta_any, meta_any) { return false; }, - [](meta_handle, meta_any) -> meta_any { return Data; }, - []() -> meta_data { - return &node; - } + 1u, + internal::meta_node>::resolve(), + &meta_arg>>, + &meta_setter, + &meta_getter + // tricks clang-format }; - node.prop = properties>(std::forward(property)...); - curr = &node; - } else if constexpr(std::is_member_object_pointer_v) { - using data_type = std::remove_reference_t().*Data)>; - - static internal::meta_data_node node{ - &internal::meta_info::template data, - {}, - type, - nullptr, - nullptr, - std::is_const_v, - !std::is_member_object_pointer_v, - &internal::meta_info::resolve, - &internal::setter, Type, Data>, - &internal::getter, - []() -> meta_data { - return &node; - } - }; - - node.prop = properties>(std::forward(property)...); - curr = &node; + link_data_if_required(id, node); + return meta_factory, std::integral_constant>{&node.prop}; } else { - static_assert(std::is_pointer_v); - using data_type = std::remove_pointer_t; + using data_type = std::remove_reference_t>; static internal::meta_data_node node{ - &internal::meta_info::template data, {}, - type, + ((std::is_same_v> || std::is_const_v) ? internal::meta_traits::is_const : internal::meta_traits::is_none) | internal::meta_traits::is_static, nullptr, nullptr, - std::is_const_v, - !std::is_member_object_pointer_v, - &internal::meta_info::resolve, - &internal::setter, Type, Data>, - &internal::getter, - []() -> meta_data { - return &node; - } + 1u, + internal::meta_node>::resolve(), + &meta_arg>>, + &meta_setter, + &meta_getter + // tricks clang-format }; - node.prop = properties>(std::forward(property)...); - curr = &node; + link_data_if_required(id, node); + return meta_factory>{&node.prop}; } - - curr->name = hashed_string{str}; - curr->next = type->data; - ENTT_ASSERT(!duplicate(hashed_string{str}, curr->next)); - ENTT_ASSERT((!internal::meta_info::template data)); - internal::meta_info::template data = curr; - type->data = curr; - - return *this; } /** @@ -487,7 +447,7 @@ public: * * 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 pointer to + * 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.
@@ -497,191 +457,194 @@ public: * * @tparam Setter The actual function to use as a setter. * @tparam Getter The actual function to use as a getter. - * @tparam Property Types of properties to assign to the meta data. - * @param str The name to assign to the meta data. - * @param property Properties to assign to the meta data. - * @return A meta factory for the parent type. + * @tparam Policy Optional policy (no policy set by default). + * @param id Unique identifier. + * @return An extended meta factory for the parent type. */ - template - meta_factory data(const char *str, Property &&... property) ENTT_NOEXCEPT { - using owner_type = std::tuple, std::integral_constant>; - using underlying_type = std::invoke_result_t; - static_assert(std::is_invocable_v); - auto * const type = internal::meta_info::resolve(); + template + auto data(const id_type id) ENTT_NOEXCEPT { + using data_type = std::invoke_result_t; + static_assert(Policy::template value, "Invalid return type for the given policy"); - static internal::meta_data_node node{ - &internal::meta_info::template data, - {}, - type, - nullptr, - nullptr, - false, - false, - &internal::meta_info::resolve, - &internal::setter, - &internal::getter, - []() -> meta_data { - return &node; - } - }; + if constexpr(std::is_same_v) { + static internal::meta_data_node node{ + {}, + /* this is never static */ + internal::meta_traits::is_const, + nullptr, + nullptr, + 0u, + internal::meta_node>>::resolve(), + &meta_arg>, + &meta_setter, + &meta_getter + // tricks clang-format + }; + + link_data_if_required(id, node); + return meta_factory, std::integral_constant>{&node.prop}; + } else { + using args_type = typename meta_function_helper_t::args_type; + + static internal::meta_data_node node{ + {}, + /* this is never static nor const */ + internal::meta_traits::is_none, + nullptr, + nullptr, + 1u, + internal::meta_node>>::resolve(), + &meta_arg>>, + &meta_setter, + &meta_getter + // tricks clang-format + }; - node.name = hashed_string{str}; - node.next = type->data; - node.prop = properties(std::forward(property)...); - ENTT_ASSERT(!duplicate(hashed_string{str}, node.next)); - ENTT_ASSERT((!internal::meta_info::template data)); - internal::meta_info::template data = &node; - type->data = &node; + link_data_if_required(id, node); + return meta_factory, std::integral_constant>{&node.prop}; + } + } - return *this; + /** + * @brief Assigns a meta data to a meta type by means of its setters and + * getter. + * + * Multi-setter support for meta data members. All setters are tried in the + * order of definition before returning to the caller.
+ * Setters can be either free functions, member functions or a mix of them + * and are provided via a `value_list` type. + * + * @sa data + * + * @tparam Setter The actual functions to use as setters. + * @tparam Getter The actual getter function. + * @tparam Policy Optional policy (no policy set by default). + * @param id Unique identifier. + * @return An extended meta factory for the parent type. + */ + template + auto data(const id_type id) ENTT_NOEXCEPT { + return data(id, std::make_index_sequence{}); } /** - * @brief Assigns a meta funcion to a meta type. + * @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 Func The actual function to attach to the meta type. - * @tparam Property Types of properties to assign to the meta function. - * @param str The name to assign to the meta function. - * @param property Properties to assign to the meta function. - * @return A meta factory for the parent type. + * @tparam Candidate The actual function to attach to the meta type. + * @tparam Policy Optional policy (no policy set by default). + * @param id Unique identifier. + * @return An extended meta factory for the parent type. */ - template - meta_factory func(const char *str, Property &&... property) ENTT_NOEXCEPT { - using owner_type = std::integral_constant; - using func_type = internal::meta_function_helper>; - auto * const type = internal::meta_info::resolve(); + template + auto func(const id_type id) ENTT_NOEXCEPT { + using descriptor = meta_function_helper_t; + static_assert(Policy::template value, "Invalid return type for the given policy"); static internal::meta_func_node node{ - &internal::meta_info::template func, {}, - type, + (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), nullptr, nullptr, - func_type::size, - func_type::is_const, - func_type::is_static, - &internal::meta_info::resolve, - &func_type::arg, - [](meta_handle handle, meta_any *any) { - return internal::invoke(handle, any, std::make_index_sequence{}); - }, - []() -> meta_func { - return &node; - } + descriptor::args_type::size, + internal::meta_node, void, std::remove_cv_t>>>::resolve(), + &meta_arg, + &meta_invoke + // tricks clang-format }; - node.name = hashed_string{str}; - node.next = type->func; - node.prop = properties(std::forward(property)...); - ENTT_ASSERT(!duplicate(hashed_string{str}, node.next)); - ENTT_ASSERT((!internal::meta_info::template func)); - internal::meta_info::template func = &node; - type->func = &node; - - return *this; + link_func_if_required(id, node); + return meta_factory>{&node.prop}; } -}; +private: + internal::meta_type_node *owner; +}; /** - * @brief Basic function to use for reflection. + * @brief Utility function to use for reflection. * * This is the point from which everything starts.
* By invoking this function with a type that is not yet reflected, a meta type - * is created to which it will be possible to attach data and functions through - * a dedicated factory. + * is created to which it will be possible to attach meta objects through a + * dedicated factory. * * @tparam Type Type to reflect. - * @tparam Property Types of properties to assign to the reflected type. - * @param str The name to assign to the reflected type. - * @param property Properties to assign to the reflected type. * @return A meta factory for the given type. */ -template -inline meta_factory reflect(const char *str, Property &&... property) ENTT_NOEXCEPT { - return meta_factory{}.type(hashed_string{str}, std::forward(property)...); +template +[[nodiscard]] auto meta() ENTT_NOEXCEPT { + auto *const node = internal::meta_node::resolve(); + // extended meta factory to allow assigning properties to opaque meta types + return meta_factory{&node->prop}; } - /** - * @brief Basic function to use for reflection. + * @brief Resets a type and all its parts. * - * This is the point from which everything starts.
- * By invoking this function with a type that is not yet reflected, a meta type - * is created to which it will be possible to attach data and functions through - * a dedicated factory. + * 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. * - * @tparam Type Type to reflect. - * @return A meta factory for the given type. + * The type is also removed from the list of searchable types. + * + * @param id Unique identifier. */ -template -inline meta_factory reflect() ENTT_NOEXCEPT { - return meta_factory{}; +inline void meta_reset(const id_type id) ENTT_NOEXCEPT { + auto clear_chain = [](auto **curr, auto... member) { + for(; *curr; *curr = std::exchange((*curr)->next, nullptr)) { + if constexpr(sizeof...(member) != 0u) { + static_assert(sizeof...(member) == 1u, "Assert in defense of the future me"); + for(auto **sub = (&((*curr)->*member), ...); *sub; *sub = std::exchange((*sub)->next, nullptr)) {} + } + } + }; + + for(auto **it = internal::meta_context::global(); *it; it = &(*it)->next) { + if(auto *node = *it; node->id == id) { + clear_chain(&node->prop); + clear_chain(&node->base); + clear_chain(&node->conv); + clear_chain(&node->ctor); + clear_chain(&node->data, &internal::meta_data_node::prop); + clear_chain(&node->func, &internal::meta_func_node::prop); + + node->id = {}; + node->dtor = nullptr; + *it = std::exchange(node->next, nullptr); + + break; + } + } } - /** - * @brief Basic function to unregister a type. + * @brief Resets a type and all its parts. * - * This function unregisters 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 unregistered but the link between the two types is - * removed. + * @sa meta_reset * - * @tparam Type Type to unregister. - * @return True if the type to unregister exists, false otherwise. + * @tparam Type Type to reset. */ template -inline bool unregister() ENTT_NOEXCEPT { - return meta_factory().unregister(); -} - - -/** - * @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 -inline meta_type resolve() ENTT_NOEXCEPT { - return internal::meta_info::resolve()->meta(); -} - - -/** - * @brief Returns the meta type associated with a given name. - * @param str The name to use to search for a meta type. - * @return The meta type associated with the given name, if any. - */ -inline meta_type resolve(const char *str) ENTT_NOEXCEPT { - const auto *curr = internal::find_if([name = hashed_string{str}](auto *node) { - return node->name == name; - }, internal::meta_info<>::type); - - return curr ? curr->meta() : meta_type{}; +void meta_reset() ENTT_NOEXCEPT { + meta_reset(internal::meta_node::resolve()->id); } - /** - * @brief Iterates all the reflected types. - * @tparam Op Type of the function object to invoke. - * @param op A valid function object. + * @brief Resets all searchable types. + * + * @sa meta_reset */ -template -void resolve(Op op) ENTT_NOEXCEPT { - internal::iterate([op = std::move(op)](auto *node) { - op(node->meta()); - }, internal::meta_info<>::type); -} - - +inline void meta_reset() ENTT_NOEXCEPT { + while(*internal::meta_context::global()) { + meta_reset((*internal::meta_context::global())->id); + } } +} // namespace entt -#endif // ENTT_META_FACTORY_HPP +#endif diff --git a/modules/entt/src/entt/meta/fwd.hpp b/modules/entt/src/entt/meta/fwd.hpp new file mode 100644 index 0000000..fa0c57a --- /dev/null +++ b/modules/entt/src/entt/meta/fwd.hpp @@ -0,0 +1,24 @@ +#ifndef ENTT_META_FWD_HPP +#define ENTT_META_FWD_HPP + +namespace entt { + +class meta_sequence_container; + +class meta_associative_container; + +class meta_any; + +struct meta_handle; + +struct meta_prop; + +struct meta_data; + +struct meta_func; + +class meta_type; + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/meta/meta.hpp b/modules/entt/src/entt/meta/meta.hpp index ddaebcd..fb2acc3 100644 --- a/modules/entt/src/entt/meta/meta.hpp +++ b/modules/entt/src/entt/meta/meta.hpp @@ -1,2275 +1,1787 @@ #ifndef ENTT_META_META_HPP #define ENTT_META_META_HPP - -#include -#include -#include -#include #include -#include -#include +#include +#include #include +#include #include "../config/config.h" -#include "../core/hashed_string.hpp" - +#include "../core/any.hpp" +#include "../core/fwd.hpp" +#include "../core/iterator.hpp" +#include "../core/type_info.hpp" +#include "../core/type_traits.hpp" +#include "../core/utility.hpp" +#include "adl_pointer.hpp" +#include "ctx.hpp" +#include "fwd.hpp" +#include "node.hpp" +#include "range.hpp" +#include "type_traits.hpp" namespace entt { - class meta_any; -class meta_handle; -class meta_prop; -class meta_base; -class meta_conv; -class meta_ctor; -class meta_dtor; -class meta_data; -class meta_func; class meta_type; +/*! @brief Proxy object for sequence containers. */ +class meta_sequence_container { + class meta_iterator; -/** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. - */ - - -namespace internal { - - -struct meta_type_node; - - -struct meta_prop_node { - meta_prop_node * next; - meta_any(* const key)(); - meta_any(* const value)(); - meta_prop(* const meta)(); -}; - - -struct meta_base_node { - meta_base_node ** const underlying; - meta_type_node * const parent; - meta_base_node * next; - meta_type_node *(* const type)(); - void *(* const cast)(void *); - meta_base(* const meta)(); -}; - - -struct meta_conv_node { - meta_conv_node ** const underlying; - meta_type_node * const parent; - meta_conv_node * next; - meta_type_node *(* const type)(); - meta_any(* const conv)(void *); - meta_conv(* const meta)(); -}; - - -struct meta_ctor_node { +public: + /*! @brief Unsigned integer type. */ using size_type = std::size_t; - meta_ctor_node ** const underlying; - meta_type_node * const parent; - meta_ctor_node * next; - meta_prop_node * prop; - const size_type size; - meta_type_node *(* const arg)(size_type); - meta_any(* const invoke)(meta_any * const); - meta_ctor(* const meta)(); -}; - - -struct meta_dtor_node { - meta_dtor_node ** const underlying; - meta_type_node * const parent; - bool(* const invoke)(meta_handle); - meta_dtor(* const meta)(); -}; - + /*! @brief Meta iterator type. */ + using iterator = meta_iterator; -struct meta_data_node { - meta_data_node ** const underlying; - hashed_string name; - meta_type_node * const parent; - meta_data_node * next; - meta_prop_node * prop; - const bool is_const; - const bool is_static; - meta_type_node *(* const type)(); - bool(* const set)(meta_handle, meta_any, meta_any); - meta_any(* const get)(meta_handle, meta_any); - meta_data(* const meta)(); -}; + /*! @brief Default constructor. */ + meta_sequence_container() ENTT_NOEXCEPT = default; + /** + * @brief Construct a proxy object for sequence containers. + * @tparam Type Type of container to wrap. + * @param instance The container to wrap. + */ + template + meta_sequence_container(std::in_place_type_t, any instance) ENTT_NOEXCEPT + : value_type_node{internal::meta_node>>::resolve()}, + size_fn{&meta_sequence_container_traits::size}, + resize_fn{&meta_sequence_container_traits::resize}, + iter_fn{&meta_sequence_container_traits::iter}, + insert_fn{&meta_sequence_container_traits::insert}, + erase_fn{&meta_sequence_container_traits::erase}, + storage{std::move(instance)} {} + + [[nodiscard]] inline meta_type value_type() const ENTT_NOEXCEPT; + [[nodiscard]] inline size_type size() const ENTT_NOEXCEPT; + inline bool resize(const size_type); + inline bool clear(); + [[nodiscard]] inline iterator begin(); + [[nodiscard]] inline iterator end(); + inline iterator insert(iterator, meta_any); + inline iterator erase(iterator); + [[nodiscard]] inline meta_any operator[](const size_type); + [[nodiscard]] inline explicit operator bool() const ENTT_NOEXCEPT; -struct meta_func_node { - using size_type = std::size_t; - meta_func_node ** const underlying; - hashed_string name; - meta_type_node * const parent; - meta_func_node * next; - meta_prop_node * prop; - const size_type size; - const bool is_const; - const bool is_static; - meta_type_node *(* const ret)(); - meta_type_node *(* const arg)(size_type); - meta_any(* const invoke)(meta_handle, meta_any *); - meta_func(* const meta)(); +private: + internal::meta_type_node *value_type_node = nullptr; + size_type (*size_fn)(const any &) ENTT_NOEXCEPT = nullptr; + bool (*resize_fn)(any &, size_type) = nullptr; + iterator (*iter_fn)(any &, const bool) = nullptr; + iterator (*insert_fn)(any &, const std::ptrdiff_t, meta_any &) = nullptr; + iterator (*erase_fn)(any &, const std::ptrdiff_t) = nullptr; + any storage{}; }; +/*! @brief Proxy object for associative containers. */ +class meta_associative_container { + class meta_iterator; -struct meta_type_node { +public: + /*! @brief Unsigned integer type. */ using size_type = std::size_t; - hashed_string name; - meta_type_node * next; - meta_prop_node * prop; - const bool is_void; - const bool is_integral; - const bool is_floating_point; - const bool is_array; - const bool is_enum; - const bool is_union; - const bool is_class; - const bool is_pointer; - const bool is_function; - const bool is_member_object_pointer; - const bool is_member_function_pointer; - const size_type extent; - meta_type(* const remove_pointer)(); - bool(* const destroy)(meta_handle); - meta_type(* const meta)(); - meta_base_node *base; - meta_conv_node *conv; - meta_ctor_node *ctor; - meta_dtor_node *dtor; - meta_data_node *data; - meta_func_node *func; -}; - - -template -struct meta_node { - inline static meta_type_node *type = nullptr; -}; - - -template -struct meta_node { - inline static meta_type_node *type = nullptr; - - template - inline static meta_base_node *base = nullptr; - - template - inline static meta_conv_node *conv = nullptr; - - template - inline static meta_ctor_node *ctor = nullptr; + /*! @brief Meta iterator type. */ + using iterator = meta_iterator; - template - inline static meta_dtor_node *dtor = nullptr; - - template - inline static meta_data_node *data = nullptr; - - template - inline static meta_func_node *func = nullptr; - - inline static meta_type_node * resolve() ENTT_NOEXCEPT; -}; - - -template -struct meta_info: meta_node>...> {}; - - -template -void iterate(Op op, const Node *curr) ENTT_NOEXCEPT { - while(curr) { - op(curr); - curr = curr->next; - } -} - - -template -void iterate(Op op, const meta_type_node *node) ENTT_NOEXCEPT { - if(node) { - auto *curr = node->base; - iterate(op, node->*Member); - - while(curr) { - iterate(op, curr->type()); - curr = curr->next; - } - } -} - - -template -auto find_if(Op op, const Node *curr) ENTT_NOEXCEPT { - while(curr && !op(curr)) { - curr = curr->next; - } - - return curr; -} - - -template -auto find_if(Op op, const meta_type_node *node) ENTT_NOEXCEPT --> decltype(find_if(op, node->*Member)) { - decltype(find_if(op, node->*Member)) ret = nullptr; - - if(node) { - ret = find_if(op, node->*Member); - auto *curr = node->base; + /*! @brief Default constructor. */ + meta_associative_container() ENTT_NOEXCEPT = default; - while(curr && !ret) { - ret = find_if(op, curr->type()); - curr = curr->next; + /** + * @brief Construct a proxy object for associative containers. + * @tparam Type Type of container to wrap. + * @param instance The container to wrap. + */ + template + meta_associative_container(std::in_place_type_t, any instance) ENTT_NOEXCEPT + : key_only_container{meta_associative_container_traits::key_only}, + key_type_node{internal::meta_node>>::resolve()}, + mapped_type_node{nullptr}, + value_type_node{internal::meta_node>>::resolve()}, + size_fn{&meta_associative_container_traits::size}, + clear_fn{&meta_associative_container_traits::clear}, + iter_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}, + storage{std::move(instance)} { + if constexpr(!meta_associative_container_traits::key_only) { + mapped_type_node = internal::meta_node>>::resolve(); } } - return ret; -} - - -template -const Type * try_cast(const meta_type_node *node, void *instance) ENTT_NOEXCEPT { - const auto *type = meta_info::resolve(); - void *ret = nullptr; - - if(node == type) { - ret = instance; - } else { - const auto *base = find_if<&meta_type_node::base>([type](auto *candidate) { - return candidate->type() == type; - }, node); - - ret = base ? base->cast(instance) : nullptr; - } - - return static_cast(ret); -} - - -template -inline bool can_cast_or_convert(const meta_type_node *from, const meta_type_node *to) ENTT_NOEXCEPT { - return (from == to) || find_if([to](auto *node) { - return node->type() == to; - }, from); -} - - -template -inline auto ctor(std::index_sequence, const meta_type_node *node) ENTT_NOEXCEPT { - return internal::find_if([](auto *candidate) { - return candidate->size == sizeof...(Args) && - (([](auto *from, auto *to) { - return internal::can_cast_or_convert<&internal::meta_type_node::base>(from, to) - || internal::can_cast_or_convert<&internal::meta_type_node::conv>(from, to); - }(internal::meta_info::resolve(), candidate->arg(Indexes))) && ...); - }, node->ctor); -} - - -} - - -/** - * Internal details not to be documented. - * @endcond TURN_OFF_DOXYGEN - */ + [[nodiscard]] inline bool key_only() const ENTT_NOEXCEPT; + [[nodiscard]] inline meta_type key_type() const ENTT_NOEXCEPT; + [[nodiscard]] inline meta_type mapped_type() const ENTT_NOEXCEPT; + [[nodiscard]] inline meta_type value_type() const ENTT_NOEXCEPT; + [[nodiscard]] inline size_type size() const ENTT_NOEXCEPT; + inline bool clear(); + [[nodiscard]] inline iterator begin(); + [[nodiscard]] inline iterator end(); + inline bool insert(meta_any, meta_any); + inline bool erase(meta_any); + [[nodiscard]] inline iterator find(meta_any); + [[nodiscard]] inline explicit operator bool() const ENTT_NOEXCEPT; +private: + bool key_only_container{}; + internal::meta_type_node *key_type_node = nullptr; + internal::meta_type_node *mapped_type_node = nullptr; + internal::meta_type_node *value_type_node = nullptr; + size_type (*size_fn)(const any &) ENTT_NOEXCEPT = nullptr; + bool (*clear_fn)(any &) = nullptr; + iterator (*iter_fn)(any &, const bool) = nullptr; + bool (*insert_fn)(any &, meta_any &, meta_any &) = nullptr; + bool (*erase_fn)(any &, meta_any &) = nullptr; + iterator (*find_fn)(any &, meta_any &) = nullptr; + any storage{}; +}; -/** - * @brief Meta any object. - * - * A meta any is an opaque container for single values of any type. - * - * This class uses a technique called small buffer optimization (SBO) to - * completely eliminate the need to allocate memory, where possible.
- * From the user's point of view, nothing will change, but the elimination of - * allocations will reduce the jumps in memory and therefore will avoid chasing - * of pointers. This will greatly improve the use of the cache, thus increasing - * the overall performance. - */ +/*! @brief Opaque wrapper for values of any type. */ class meta_any { - /*! @brief A meta handle is allowed to _inherit_ from a meta any. */ - friend class meta_handle; - - using storage_type = std::aligned_storage_t; - using compare_fn_type = bool(*)(const void *, const void *); - using copy_fn_type = void *(*)(storage_type &, const void *); - using destroy_fn_type = void(*)(storage_type &); - - template - inline static auto compare(int, const Type &lhs, const Type &rhs) - -> decltype(lhs == rhs, bool{}) { - return lhs == rhs; - } - - template - inline static bool compare(char, const Type &lhs, const Type &rhs) { - return &lhs == &rhs; - } + enum class operation : std::uint8_t { + deref, + seq, + assoc + }; - template - static bool compare(const void *lhs, const void *rhs) { - return compare(0, *static_cast(lhs), *static_cast(rhs)); - } + using vtable_type = void(const operation, const any &, void *); template - static void * copy_storage(storage_type &storage, const void *instance) { - return new (&storage) Type{*static_cast(instance)}; - } - - template - static void * copy_object(storage_type &storage, const void *instance) { - using chunk_type = std::aligned_storage_t; - auto *chunk = new chunk_type; - new (&storage) chunk_type *{chunk}; - return new (chunk) Type{*static_cast(instance)}; + static void basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const any &value, [[maybe_unused]] void *other) { + static_assert(std::is_same_v>, Type>, "Invalid type"); + + if constexpr(!std::is_void_v) { + switch(op) { + case operation::deref: + if constexpr(is_meta_pointer_like_v) { + if constexpr(std::is_function_v::element_type>>) { + *static_cast(other) = any_cast(value); + } else if constexpr(!std::is_same_v::element_type>, void>) { + using in_place_type = decltype(adl_meta_pointer_like::dereference(any_cast(value))); + + if constexpr(std::is_constructible_v) { + if(const auto &pointer_like = any_cast(value); pointer_like) { + static_cast(other)->emplace(adl_meta_pointer_like::dereference(pointer_like)); + } + } else { + static_cast(other)->emplace(adl_meta_pointer_like::dereference(any_cast(value))); + } + } + } + break; + case operation::seq: + if constexpr(is_complete_v>) { + *static_cast(other) = {std::in_place_type, std::move(const_cast(value))}; + } + break; + case operation::assoc: + if constexpr(is_complete_v>) { + *static_cast(other) = {std::in_place_type, std::move(const_cast(value))}; + } + break; + } + } } - template - static void destroy_storage(storage_type &storage) { - auto *node = internal::meta_info::resolve(); - auto *instance = reinterpret_cast(&storage); - node->dtor ? node->dtor->invoke(*instance) : node->destroy(*instance); + void release() { + if(node && node->dtor && storage.owner()) { + node->dtor(storage.data()); + } } - template - static void destroy_object(storage_type &storage) { - using chunk_type = std::aligned_storage_t; - auto *node = internal::meta_info::resolve(); - auto *chunk = *reinterpret_cast(&storage); - auto *instance = reinterpret_cast(chunk); - node->dtor ? node->dtor->invoke(*instance) : node->destroy(*instance); - delete chunk; - } + meta_any(const meta_any &other, any ref) ENTT_NOEXCEPT + : storage{std::move(ref)}, + node{storage ? other.node : nullptr}, + vtable{storage ? other.vtable : &basic_vtable} {} public: /*! @brief Default constructor. */ meta_any() ENTT_NOEXCEPT : storage{}, - instance{nullptr}, - node{nullptr}, - destroy_fn{nullptr}, - compare_fn{nullptr}, - copy_fn{nullptr} - {} + node{}, + vtable{&basic_vtable} {} /** - * @brief Constructs a meta any from a given value. - * - * This class uses a technique called small buffer optimization (SBO) to - * completely eliminate the need to allocate memory, where possible.
- * From the user's point of view, nothing will change, but the elimination - * of allocations will reduce the jumps in memory and therefore will avoid - * chasing of pointers. This will greatly improve the use of the cache, thus - * increasing the overall performance. - * - * @tparam Type Type of object to use to initialize the container. - * @param type An instance of an object to use to initialize the container. + * @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>, meta_any>>> - meta_any(Type &&type) { - using actual_type = std::remove_cv_t>; - node = internal::meta_info::resolve(); - - compare_fn = &compare; - - if constexpr(sizeof(actual_type) <= sizeof(void *)) { - instance = new (&storage) actual_type{std::forward(type)}; - destroy_fn = &destroy_storage; - copy_fn = ©_storage; - } else { - using chunk_type = std::aligned_storage_t; + template + explicit meta_any(std::in_place_type_t, Args &&...args) + : storage{std::in_place_type, std::forward(args)...}, + node{internal::meta_node>>::resolve()}, + vtable{&basic_vtable>>} {} - auto *chunk = new chunk_type; - instance = new (chunk) actual_type{std::forward(type)}; - new (&storage) chunk_type *{chunk}; - - destroy_fn = &destroy_object; - copy_fn = ©_object; - } - } + /** + * @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, meta_any>>> + meta_any(Type &&value) + : meta_any{std::in_place_type>>, std::forward(value)} {} /** * @brief Copy constructor. * @param other The instance to copy from. */ - meta_any(const meta_any &other) - : meta_any{} - { - if(other) { - instance = other.copy_fn(storage, other.instance); - node = other.node; - destroy_fn = other.destroy_fn; - compare_fn = other.compare_fn; - copy_fn = other.copy_fn; - } - } + meta_any(const meta_any &other) = default; /** * @brief Move constructor. - * - * After meta any move construction, instances that have been moved from - * are placed in a valid but unspecified state. It's highly discouraged to - * continue using them. - * * @param other The instance to move from. */ meta_any(meta_any &&other) ENTT_NOEXCEPT - : meta_any{} - { - swap(*this, other); - } + : storage{std::move(other.storage)}, + node{std::exchange(other.node, nullptr)}, + vtable{std::exchange(other.vtable, &basic_vtable)} {} /*! @brief Frees the internal storage, whatever it means. */ ~meta_any() { - if(destroy_fn) { - destroy_fn(storage); - } + release(); } /** - * @brief Assignment operator. - * @param other The instance to assign. + * @brief Copy assignment operator. + * @param other The instance to copy from. * @return This meta any object. */ - meta_any & operator=(meta_any other) { - swap(other, *this); + meta_any &operator=(const meta_any &other) { + release(); + vtable = other.vtable; + storage = other.storage; + node = other.node; return *this; } /** - * @brief Returns the meta type of the underlying object. - * @return The meta type of the underlying object, if any. + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This meta any object. */ - inline meta_type type() const ENTT_NOEXCEPT; + meta_any &operator=(meta_any &&other) ENTT_NOEXCEPT { + release(); + vtable = std::exchange(other.vtable, &basic_vtable); + storage = std::move(other.storage); + node = std::exchange(other.node, nullptr); + return *this; + } /** - * @brief Returns an opaque pointer to the contained instance. - * @return An opaque pointer the contained instance, if any. + * @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 meta any object. */ - inline const void * data() const ENTT_NOEXCEPT { - return instance; + template + std::enable_if_t, meta_any>, meta_any &> + operator=(Type &&value) { + emplace>(std::forward(value)); + return *this; } - /*! @copydoc data */ - inline void * data() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).data()); + /*! @copydoc any::type */ + [[nodiscard]] inline meta_type type() const ENTT_NOEXCEPT; + + /*! @copydoc any::data */ + [[nodiscard]] const void *data() const ENTT_NOEXCEPT { + return storage.data(); } - /** - * @brief Checks if it's possible to cast an instance to a given type. - * @tparam Type Type to which to cast the instance. - * @return True if the cast is viable, false otherwise. - */ - template - inline bool can_cast() const ENTT_NOEXCEPT { - const auto *type = internal::meta_info::resolve(); - return internal::can_cast_or_convert<&internal::meta_type_node::base>(node, type); + /*! @copydoc any::data */ + [[nodiscard]] void *data() ENTT_NOEXCEPT { + return storage.data(); } /** - * @brief Tries to cast an instance to a given type. - * - * The type of the instance must be such that the cast is possible. + * @brief Invokes the underlying function, if possible. * - * @warning - * Attempting to perform a cast that isn't viable results in undefined - * behavior.
- * An assertion will abort the execution at runtime in debug mode in case - * the cast is not feasible. + * @sa meta_func::invoke * - * @tparam Type Type to which to cast the instance. - * @return A reference to the contained instance. + * @tparam Args Types of arguments to use to invoke the function. + * @param id Unique identifier. + * @param args Parameters to use to invoke the function. + * @return A wrapper containing the returned value, if any. */ - template - inline const Type & cast() const ENTT_NOEXCEPT { - ENTT_ASSERT(can_cast()); - return *internal::try_cast(node, instance); - } + template + meta_any invoke(const id_type id, Args &&...args) const; - /*! @copydoc cast */ - template - inline Type & cast() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).cast()); - } + /*! @copydoc invoke */ + template + meta_any invoke(const id_type id, Args &&...args); /** - * @brief Checks if it's possible to convert an instance to a given type. - * @tparam Type Type to which to convert the instance. - * @return True if the conversion is viable, false otherwise. + * @brief Sets the value of a given variable. + * + * The type of the value is such that a cast or conversion to the type of + * the variable is possible. Otherwise, invoking the setter does nothing. + * + * @tparam Type Type of value to assign. + * @param id Unique identifier. + * @param value Parameter to use to set the underlying variable. + * @return True in case of success, false otherwise. */ template - inline bool can_convert() const ENTT_NOEXCEPT { - const auto *type = internal::meta_info::resolve(); - return internal::can_cast_or_convert<&internal::meta_type_node::conv>(node, type); - } + bool set(const id_type id, Type &&value); /** - * @brief Tries to convert an instance to a given type and returns it. - * @tparam Type Type to which to convert the instance. - * @return A valid meta any object if the conversion is possible, an invalid - * one otherwise. + * @brief Gets the value of a given variable. + * @param id Unique identifier. + * @return A wrapper containing the value of the underlying variable. */ - template - inline meta_any convert() const ENTT_NOEXCEPT { - const auto *type = internal::meta_info::resolve(); - meta_any any{}; + [[nodiscard]] meta_any get(const id_type id) const; - if(node == type) { - any = *static_cast(instance); - } else { - const auto *conv = internal::find_if<&internal::meta_type_node::conv>([type](auto *other) { - return other->type() == type; - }, node); - - if(conv) { - any = conv->conv(instance); - } - } - - return any; - } + /*! @copydoc get */ + [[nodiscard]] meta_any get(const id_type id); /** - * @brief Tries to convert an instance to a given type. - * @tparam Type Type to which to convert the instance. - * @return True if the conversion is possible, false otherwise. + * @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 - inline bool convert() ENTT_NOEXCEPT { - bool valid = (node == internal::meta_info::resolve()); - - if(!valid) { - auto any = std::as_const(*this).convert(); - - if(any) { - std::swap(any, *this); - valid = true; + [[nodiscard]] const Type *try_cast() const { + if(const auto &info = type_id(); node && *node->info == info) { + return any_cast(&storage); + } else if(node) { + for(auto *it = node->base; it; it = it->next) { + const auto as_const = it->cast(as_ref()); + + if(const Type *base = as_const.template try_cast(); base) { + return base; + } } } - return valid; + return nullptr; } - /** - * @brief Returns false if a container is empty, true otherwise. - * @return False if the container is empty, true otherwise. - */ - inline explicit operator bool() const ENTT_NOEXCEPT { - return destroy_fn; - } - - /** - * @brief Checks if two containers differ in their content. - * @param other Container with which to compare. - * @return False if the two containers differ in their content, true - * otherwise. - */ - inline bool operator==(const meta_any &other) const ENTT_NOEXCEPT { - return (!instance && !other.instance) || (instance && other.instance && node == other.node && compare_fn(instance, other.instance)); - } - - /** - * @brief Swaps two meta any objects. - * @param lhs A valid meta any object. - * @param rhs A valid meta any object. - */ - friend void swap(meta_any &lhs, meta_any &rhs) { - using std::swap; - - if(lhs && rhs) { - storage_type buffer; - void *tmp = lhs.copy_fn(buffer, lhs.instance); - lhs.destroy_fn(lhs.storage); - lhs.instance = rhs.copy_fn(lhs.storage, rhs.instance); - rhs.destroy_fn(rhs.storage); - rhs.instance = lhs.copy_fn(rhs.storage, tmp); - lhs.destroy_fn(buffer); - } else if(lhs) { - rhs.instance = lhs.copy_fn(rhs.storage, lhs.instance); - lhs.destroy_fn(lhs.storage); - lhs.instance = nullptr; - } else if(rhs) { - lhs.instance = rhs.copy_fn(lhs.storage, rhs.instance); - rhs.destroy_fn(rhs.storage); - rhs.instance = nullptr; + /*! @copydoc try_cast */ + template + [[nodiscard]] Type *try_cast() { + if(const auto &info = type_id(); node && *node->info == info) { + return any_cast(&storage); + } else if(node) { + for(auto *it = node->base; it; it = it->next) { + if(Type *base = it->cast(as_ref()).template try_cast(); base) { + return base; + } + } } - std::swap(lhs.node, rhs.node); - std::swap(lhs.destroy_fn, rhs.destroy_fn); - std::swap(lhs.compare_fn, rhs.compare_fn); - std::swap(lhs.copy_fn, rhs.copy_fn); + return nullptr; } -private: - storage_type storage; - void *instance; - internal::meta_type_node *node; - destroy_fn_type destroy_fn; - compare_fn_type compare_fn; - copy_fn_type copy_fn; -}; - - -/** - * @brief Meta handle object. - * - * A meta handle is an opaque pointer to an instance of any type. - * - * A handle doesn't perform copies and isn't responsible for the contained - * object. It doesn't prolong the lifetime of the pointed instance. Users are - * responsible for ensuring that the target object remains alive for the entire - * interval of use of the handle. - */ -class meta_handle { - meta_handle(int, meta_any &any) ENTT_NOEXCEPT - : node{any.node}, - instance{any.instance} - {} - - template - meta_handle(char, Type &&obj) ENTT_NOEXCEPT - : node{internal::meta_info::resolve()}, - instance{&obj} - {} - -public: - /*! @brief Default constructor. */ - meta_handle() ENTT_NOEXCEPT - : node{nullptr}, - instance{nullptr} - {} - - /** - * @brief Constructs a meta handle from a given instance. - * @tparam Type Type of object to use to initialize the handle. - * @param obj A reference to an object to use to initialize the handle. - */ - template>, meta_handle>>> - meta_handle(Type &&obj) ENTT_NOEXCEPT - : meta_handle{0, std::forward(obj)} - {} - - /** - * @brief Returns the meta type of the underlying object. - * @return The meta type of the underlying object, if any. - */ - inline meta_type type() const ENTT_NOEXCEPT; - /** * @brief Tries to cast an instance to a given type. * - * The type of the instance must be such that the conversion is possible. + * The type of the instance must be such that the cast is possible. * * @warning - * Attempting to perform a conversion that isn't viable results in undefined - * behavior.
- * An assertion will abort the execution at runtime in debug mode in case - * the conversion is not feasible. + * Attempting to perform an invalid cast results is undefined behavior. * * @tparam Type Type to which to cast the instance. - * @return A pointer to the contained instance. + * @return A reference to the contained instance. */ template - inline const Type * try_cast() const ENTT_NOEXCEPT { - return internal::try_cast(node, instance); + [[nodiscard]] Type cast() const { + auto *const instance = try_cast>(); + ENTT_ASSERT(instance, "Invalid instance"); + return static_cast(*instance); } - /*! @copydoc try_cast */ + /*! @copydoc cast */ template - inline Type * try_cast() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).try_cast()); + [[nodiscard]] Type 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 Returns an opaque pointer to the contained instance. - * @return An opaque pointer the contained instance, if any. + * @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 any object if there exists a viable conversion, an + * invalid one otherwise. */ - inline const void * data() const ENTT_NOEXCEPT { - return instance; - } - - /*! @copydoc data */ - inline void * data() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).data()); - } + [[nodiscard]] meta_any allow_cast(const meta_type &type) const; /** - * @brief Returns false if a handle is empty, true otherwise. - * @return False if the handle is empty, true otherwise. + * @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 there exists a viable conversion, false otherwise. */ - inline explicit operator bool() const ENTT_NOEXCEPT { - return instance; - } - -private: - const internal::meta_type_node *node; - void *instance; -}; - - -/** - * @brief Checks if two containers differ in their content. - * @param lhs A meta any object, either empty or not. - * @param rhs A meta any object, either empty or not. - * @return True if the two containers differ in their content, false otherwise. - */ -inline bool operator!=(const meta_any &lhs, const meta_any &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); -} - - -/** - * @brief Meta property object. - * - * A meta property is an opaque container for a key/value pair.
- * Properties are associated with any other meta object to enrich it. - */ -class meta_prop { - /*! @brief A meta factory is allowed to create meta objects. */ - template friend class meta_factory; - - inline meta_prop(const internal::meta_prop_node *curr) ENTT_NOEXCEPT - : node{curr} - {} + [[nodiscard]] bool allow_cast(const meta_type &type) { + if(auto other = std::as_const(*this).allow_cast(type); other) { + if(other.storage.owner()) { + std::swap(*this, other); + } -public: - /*! @brief Default constructor. */ - inline meta_prop() ENTT_NOEXCEPT - : node{nullptr} - {} + return true; + } - /** - * @brief Returns the stored key. - * @return A meta any containing the key stored with the given property. - */ - inline meta_any key() const ENTT_NOEXCEPT { - return node->key(); + return false; } /** - * @brief Returns the stored value. - * @return A meta any containing the value stored with the given property. + * @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 any object if there exists a viable conversion, an + * invalid one otherwise. */ - inline meta_any value() const ENTT_NOEXCEPT { - return node->value(); - } + template + [[nodiscard]] meta_any allow_cast() const { + const auto other = allow_cast(internal::meta_node>>::resolve()); - /** - * @brief Returns true if a meta object is valid, false otherwise. - * @return True if the meta object is valid, false otherwise. - */ - inline explicit operator bool() const ENTT_NOEXCEPT { - return node; + if constexpr(std::is_reference_v && !std::is_const_v>) { + return other.storage.owner() ? other : meta_any{}; + } else { + return other; + } } /** - * @brief Checks if two meta objects refer to the same node. - * @param other The meta object with which to compare. - * @return True if the two meta objects refer to the same node, false - * otherwise. + * @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 there exists a viable conversion, false otherwise. */ - inline bool operator==(const meta_prop &other) const ENTT_NOEXCEPT { - return node == other.node; - } - -private: - const internal::meta_prop_node *node; -}; + template + bool allow_cast() { + if(auto other = std::as_const(*this).allow_cast(internal::meta_node>>::resolve()); other) { + if(other.storage.owner()) { + std::swap(*this, other); + return true; + } + return (static_cast> &>(storage).data() != nullptr); + } -/** - * @brief Checks if two meta objects refer to the same node. - * @param lhs A meta object, either valid or not. - * @param rhs A meta object, either valid or not. - * @return True if the two meta objects refer to the same node, false otherwise. - */ -inline bool operator!=(const meta_prop &lhs, const meta_prop &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); -} + return false; + } + /*! @copydoc any::emplace */ + template + void emplace(Args &&...args) { + release(); + vtable = &basic_vtable>>; + storage.emplace(std::forward(args)...); + node = internal::meta_node>>::resolve(); + } -/** - * @brief Meta base object. - * - * A meta base is an opaque container for a base class to be used to walk - * through hierarchies. - */ -class meta_base { - /*! @brief A meta factory is allowed to create meta objects. */ - template friend class meta_factory; + /*! @copydoc any::assign */ + bool assign(const meta_any &other); - inline meta_base(const internal::meta_base_node *curr) ENTT_NOEXCEPT - : node{curr} - {} + /*! @copydoc any::assign */ + bool assign(meta_any &&other); -public: - /*! @brief Default constructor. */ - inline meta_base() ENTT_NOEXCEPT - : node{nullptr} - {} + /*! @copydoc any::reset */ + void reset() { + release(); + vtable = &basic_vtable; + storage.reset(); + node = nullptr; + } /** - * @brief Returns the meta type to which a meta base belongs. - * @return The meta type to which the meta base belongs. + * @brief Returns a sequence container proxy. + * @return A sequence container proxy for the underlying object. */ - inline meta_type parent() const ENTT_NOEXCEPT; + [[nodiscard]] meta_sequence_container as_sequence_container() ENTT_NOEXCEPT { + any detached = storage.as_ref(); + meta_sequence_container proxy; + vtable(operation::seq, detached, &proxy); + return proxy; + } - /** - * @brief Returns the meta type of a given meta base. - * @return The meta type of the meta base. - */ - inline meta_type type() const ENTT_NOEXCEPT; + /*! @copydoc as_sequence_container */ + [[nodiscard]] meta_sequence_container as_sequence_container() const ENTT_NOEXCEPT { + any detached = storage.as_ref(); + meta_sequence_container proxy; + vtable(operation::seq, detached, &proxy); + return proxy; + } /** - * @brief Casts an instance from a parent type to a base type. - * @param instance The instance to cast. - * @return An opaque pointer to the base type. + * @brief Returns an associative container proxy. + * @return An associative container proxy for the underlying object. */ - inline void * cast(void *instance) const ENTT_NOEXCEPT { - return node->cast(instance); + [[nodiscard]] meta_associative_container as_associative_container() ENTT_NOEXCEPT { + any detached = storage.as_ref(); + meta_associative_container proxy; + vtable(operation::assoc, detached, &proxy); + return proxy; } - /** - * @brief Returns true if a meta object is valid, false otherwise. - * @return True if the meta object is valid, false otherwise. - */ - inline explicit operator bool() const ENTT_NOEXCEPT { - return node; + /*! @copydoc as_associative_container */ + [[nodiscard]] meta_associative_container as_associative_container() const ENTT_NOEXCEPT { + any detached = storage.as_ref(); + meta_associative_container proxy; + vtable(operation::assoc, detached, &proxy); + return proxy; } /** - * @brief Checks if two meta objects refer to the same node. - * @param other The meta object with which to compare. - * @return True if the two meta objects refer to the same node, false - * otherwise. + * @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. */ - inline bool operator==(const meta_base &other) const ENTT_NOEXCEPT { - return node == other.node; + [[nodiscard]] meta_any operator*() const ENTT_NOEXCEPT { + meta_any ret{}; + vtable(operation::deref, storage, &ret); + return ret; } -private: - const internal::meta_base_node *node; -}; - - -/** - * @brief Checks if two meta objects refer to the same node. - * @param lhs A meta object, either valid or not. - * @param rhs A meta object, either valid or not. - * @return True if the two meta objects refer to the same node, false otherwise. - */ -inline bool operator!=(const meta_base &lhs, const meta_base &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); -} - - -/** - * @brief Meta conversion function object. - * - * A meta conversion function is an opaque container for a conversion function - * to be used to convert a given instance to another type. - */ -class meta_conv { - /*! @brief A meta factory is allowed to create meta objects. */ - template friend class meta_factory; - - inline meta_conv(const internal::meta_conv_node *curr) ENTT_NOEXCEPT - : node{curr} - {} - -public: - /*! @brief Default constructor. */ - inline meta_conv() ENTT_NOEXCEPT - : node{nullptr} - {} - /** - * @brief Returns the meta type to which a meta conversion function belongs. - * @return The meta type to which the meta conversion function belongs. + * @brief Returns false if a wrapper is invalid, true otherwise. + * @return False if the wrapper is invalid, true otherwise. */ - inline meta_type parent() const ENTT_NOEXCEPT; + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return !(node == nullptr); + } - /** - * @brief Returns the meta type of a given meta conversion function. - * @return The meta type of the meta conversion function. - */ - inline meta_type type() const ENTT_NOEXCEPT; + /*! @copydoc any::operator== */ + [[nodiscard]] bool operator==(const meta_any &other) const { + return (!node && !other.node) || (node && other.node && *node->info == *other.node->info && storage == other.storage); + } - /** - * @brief Converts an instance to a given type. - * @param instance The instance to convert. - * @return An opaque pointer to the instance to convert. - */ - inline meta_any convert(void *instance) const ENTT_NOEXCEPT { - return node->conv(instance); + /*! @copydoc any::as_ref */ + [[nodiscard]] meta_any as_ref() ENTT_NOEXCEPT { + return meta_any{*this, storage.as_ref()}; } - /** - * @brief Returns true if a meta object is valid, false otherwise. - * @return True if the meta object is valid, false otherwise. - */ - inline explicit operator bool() const ENTT_NOEXCEPT { - return node; + /*! @copydoc any::as_ref */ + [[nodiscard]] meta_any as_ref() const ENTT_NOEXCEPT { + return meta_any{*this, storage.as_ref()}; } - /** - * @brief Checks if two meta objects refer to the same node. - * @param other The meta object with which to compare. - * @return True if the two meta objects refer to the same node, false - * otherwise. - */ - inline bool operator==(const meta_conv &other) const ENTT_NOEXCEPT { - return node == other.node; + /*! @copydoc any::owner */ + [[nodiscard]] bool owner() const ENTT_NOEXCEPT { + return storage.owner(); } private: - const internal::meta_conv_node *node; + any storage; + internal::meta_type_node *node; + vtable_type *vtable; }; - /** - * @brief Checks if two meta objects refer to the same node. - * @param lhs A meta object, either valid or not. - * @param rhs A meta object, either valid or not. - * @return True if the two meta objects refer to the same node, false otherwise. + * @brief Checks if two wrappers differ in their content. + * @param lhs A wrapper, either empty or not. + * @param rhs A wrapper, either empty or not. + * @return True if the two wrappers differ in their content, false otherwise. */ -inline bool operator!=(const meta_conv &lhs, const meta_conv &rhs) ENTT_NOEXCEPT { +[[nodiscard]] inline bool operator!=(const meta_any &lhs, const meta_any &rhs) ENTT_NOEXCEPT { return !(lhs == rhs); } - /** - * @brief Meta constructor object. - * - * A meta constructor is an opaque container for a function to be used to - * construct instances of a given type. + * @brief Constructs a wrapper from a given type, passing it all arguments. + * @tparam Type Type of object to use to initialize the wrapper. + * @tparam 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. */ -class meta_ctor { - /*! @brief A meta factory is allowed to create meta objects. */ - template friend class meta_factory; - - inline meta_ctor(const internal::meta_ctor_node *curr) ENTT_NOEXCEPT - : node{curr} - {} +template +meta_any make_meta(Args &&...args) { + return meta_any{std::in_place_type, std::forward(args)...}; +} -public: - /*! @brief Unsigned integer type. */ - using size_type = typename internal::meta_ctor_node::size_type; +/** + * @brief Forwards its argument and avoids copies for lvalue references. + * @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 +meta_any forward_as_meta(Type &&value) { + return meta_any{std::in_place_type, std::decay_t, Type>>, std::forward(value)}; +} +/** + * @brief Opaque pointers to instances of any type. + * + * A handle doesn't perform copies and isn't responsible for the contained + * object. It doesn't prolong the lifetime of the pointed instance.
+ * Handles are used to generate references to actual objects when needed. + */ +struct meta_handle { /*! @brief Default constructor. */ - inline meta_ctor() ENTT_NOEXCEPT - : node{nullptr} - {} + meta_handle() = default; - /** - * @brief Returns the meta type to which a meta constructor belongs. - * @return The meta type to which the meta constructor belongs. - */ - inline meta_type parent() const ENTT_NOEXCEPT; + /*! @brief Default copy constructor, deleted on purpose. */ + meta_handle(const meta_handle &) = delete; - /** - * @brief Returns the number of arguments accepted by a meta constructor. - * @return The number of arguments accepted by the meta constructor. - */ - inline size_type size() const ENTT_NOEXCEPT { - return node->size; - } + /*! @brief Default move constructor. */ + meta_handle(meta_handle &&) = default; /** - * @brief Returns the meta type of the i-th argument of a meta constructor. - * @param index The index of the argument of which to return the meta type. - * @return The meta type of the i-th argument of a meta constructor, if any. + * @brief Default copy assignment operator, deleted on purpose. + * @return This meta handle. */ - inline meta_type arg(size_type index) const ENTT_NOEXCEPT; + meta_handle &operator=(const meta_handle &) = delete; /** - * @brief Creates an instance of the underlying type, if possible. - * - * To create a valid instance, the types of the parameters must coincide - * exactly with those required by the underlying meta constructor. - * Otherwise, an empty and then invalid container is returned. - * - * @tparam Args Types of arguments to use to construct the instance. - * @param args Parameters to use to construct the instance. - * @return A meta any containing the new instance, if any. + * @brief Default move assignment operator. + * @return This meta handle. */ - template - meta_any invoke(Args &&... args) const { - std::array arguments{{std::forward(args)...}}; - meta_any any{}; - - if(sizeof...(Args) == size()) { - any = node->invoke(arguments.data()); - } - - return any; - } + meta_handle &operator=(meta_handle &&) = default; /** - * @brief Iterates all the properties assigned to a meta constructor. - * @tparam Op Type of the function object to invoke. - * @param op A valid function object. + * @brief Creates a handle that points to an unmanaged object. + * @tparam Type Type of object to use to initialize the handle. + * @param value An instance of an object to use to initialize the handle. */ - template - inline std::enable_if_t, void> - prop(Op op) const ENTT_NOEXCEPT { - internal::iterate([op = std::move(op)](auto *curr) { - op(curr->meta()); - }, node->prop); + template, meta_handle>>> + meta_handle(Type &value) ENTT_NOEXCEPT + : meta_handle{} { + if constexpr(std::is_same_v, meta_any>) { + any = value.as_ref(); + } else { + any.emplace(value); + } } /** - * @brief Returns the property associated with a given key. - * @tparam Key Type of key to use to search for a property. - * @param key The key to use to search for a property. - * @return The property associated with the given key, if any. + * @brief Returns false if a handle is invalid, true otherwise. + * @return False if the handle is invalid, true otherwise. */ - template - inline std::enable_if_t, meta_prop> - prop(Key &&key) const ENTT_NOEXCEPT { - const auto *curr = internal::find_if([key = meta_any{std::forward(key)}](auto *candidate) { - return candidate->key() == key; - }, node->prop); - - return curr ? curr->meta() : meta_prop{}; + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return static_cast(any); } /** - * @brief Returns true if a meta object is valid, false otherwise. - * @return True if the meta object is valid, false otherwise. + * @brief Access operator for accessing the contained opaque object. + * @return A wrapper that shares a reference to an unmanaged object. */ - inline explicit operator bool() const ENTT_NOEXCEPT { - return node; + [[nodiscard]] meta_any *operator->() { + return &any; } - /** - * @brief Checks if two meta objects refer to the same node. - * @param other The meta object with which to compare. - * @return True if the two meta objects refer to the same node, false - * otherwise. - */ - inline bool operator==(const meta_ctor &other) const ENTT_NOEXCEPT { - return node == other.node; + /*! @copydoc operator-> */ + [[nodiscard]] const meta_any *operator->() const { + return &any; } private: - const internal::meta_ctor_node *node; + meta_any any; }; - -/** - * @brief Checks if two meta objects refer to the same node. - * @param lhs A meta object, either valid or not. - * @param rhs A meta object, either valid or not. - * @return True if the two meta objects refer to the same node, false otherwise. - */ -inline bool operator!=(const meta_ctor &lhs, const meta_ctor &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); -} - - -/** - * @brief Meta destructor object. - * - * A meta destructor is an opaque container for a function to be used to - * destroy instances of a given type. - */ -class meta_dtor { - /*! @brief A meta factory is allowed to create meta objects. */ - template friend class meta_factory; - - inline meta_dtor(const internal::meta_dtor_node *curr) ENTT_NOEXCEPT - : node{curr} - {} - -public: - /*! @brief Default constructor. */ - inline meta_dtor() ENTT_NOEXCEPT - : node{nullptr} - {} +/*! @brief Opaque wrapper for properties of any type. */ +struct meta_prop { + /*! @brief Node type. */ + using node_type = internal::meta_prop_node; /** - * @brief Returns the meta type to which a meta destructor belongs. - * @return The meta type to which the meta destructor belongs. + * @brief Constructs an instance from a given node. + * @param curr The underlying node with which to construct the instance. */ - inline meta_type parent() const ENTT_NOEXCEPT; + meta_prop(const node_type *curr = nullptr) ENTT_NOEXCEPT + : node{curr} {} /** - * @brief Destroys an instance of the underlying type. - * - * It must be possible to cast the instance to the parent type of the meta - * destructor. Otherwise, invoking the meta destructor results in an - * undefined behavior. - * - * @param handle An opaque pointer to an instance of the underlying type. - * @return True in case of success, false otherwise. + * @brief Returns the stored key as a const reference. + * @return A wrapper containing the key stored with the property. */ - inline bool invoke(meta_handle handle) const { - return node->invoke(handle); + [[nodiscard]] meta_any key() const { + return node->id.as_ref(); } /** - * @brief Returns true if a meta object is valid, false otherwise. - * @return True if the meta object is valid, false otherwise. + * @brief Returns the stored value by copy. + * @return A wrapper containing the value stored with the property. */ - inline explicit operator bool() const ENTT_NOEXCEPT { - return node; + [[nodiscard]] meta_any value() const { + return node->value; } /** - * @brief Checks if two meta objects refer to the same node. - * @param other The meta object with which to compare. - * @return True if the two meta objects refer to the same node, false - * otherwise. + * @brief Returns true if an object is valid, false otherwise. + * @return True if the object is valid, false otherwise. */ - inline bool operator==(const meta_dtor &other) const ENTT_NOEXCEPT { - return node == other.node; + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return !(node == nullptr); } private: - const internal::meta_dtor_node *node; + const node_type *node; }; +/*! @brief Opaque wrapper for data members. */ +struct meta_data { + /*! @brief Node type. */ + using node_type = internal::meta_data_node; + /*! @brief Unsigned integer type. */ + using size_type = typename node_type::size_type; -/** - * @brief Checks if two meta objects refer to the same node. - * @param lhs A meta object, either valid or not. - * @param rhs A meta object, either valid or not. - * @return True if the two meta objects refer to the same node, false otherwise. - */ -inline bool operator!=(const meta_dtor &lhs, const meta_dtor &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); -} - - -/** - * @brief Meta data object. - * - * A meta data is an opaque container for a data member associated with a given - * type. - */ -class meta_data { - /*! @brief A meta factory is allowed to create meta objects. */ - template friend class meta_factory; - - inline meta_data(const internal::meta_data_node *curr) ENTT_NOEXCEPT - : node{curr} - {} - -public: - /*! @brief Default constructor. */ - inline meta_data() ENTT_NOEXCEPT - : node{nullptr} - {} + /*! @copydoc meta_prop::meta_prop */ + meta_data(const node_type *curr = nullptr) ENTT_NOEXCEPT + : node{curr} {} - /** - * @brief Returns the name assigned to a given meta data. - * @return The name assigned to the meta data. - */ - inline const char * name() const ENTT_NOEXCEPT { - return node->name; + /*! @copydoc meta_type::id */ + [[nodiscard]] id_type id() const ENTT_NOEXCEPT { + return node->id; } /** - * @brief Returns the meta type to which a meta data belongs. - * @return The meta type to which the meta data belongs. + * @brief Returns the number of setters available. + * @return The number of setters available. */ - inline meta_type parent() const ENTT_NOEXCEPT; + [[nodiscard]] size_type arity() const ENTT_NOEXCEPT { + return node->arity; + } /** - * @brief Indicates whether a given meta data is constant or not. - * @return True if the meta data is constant, false otherwise. + * @brief Indicates whether a data member is constant or not. + * @return True if the data member is constant, false otherwise. */ - inline bool is_const() const ENTT_NOEXCEPT { - return node->is_const; + [[nodiscard]] bool is_const() const ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_const); } /** - * @brief Indicates whether a given meta data is static or not. - * - * A static meta data is such that it can be accessed using a null pointer - * as an instance. - * - * @return True if the meta data is static, false otherwise. + * @brief Indicates whether a data member is static or not. + * @return True if the data member is static, false otherwise. */ - inline bool is_static() const ENTT_NOEXCEPT { - return node->is_static; + [[nodiscard]] bool is_static() const ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_static); } - /** - * @brief Returns the meta type of a given meta data. - * @return The meta type of the meta data. - */ - inline meta_type type() const ENTT_NOEXCEPT; + /*! @copydoc meta_any::type */ + [[nodiscard]] inline meta_type type() const ENTT_NOEXCEPT; /** - * @brief Sets the value of the variable enclosed by a given meta type. + * @brief Sets the value of a given variable. * - * It must be possible to cast the instance to the parent type of the meta - * data. Otherwise, invoking the setter results in an undefined - * behavior.
- * The type of the value must coincide exactly with that of the variable - * enclosed by the meta data. Otherwise, invoking the setter does nothing. + * It must be possible to cast the instance to the parent type of the data + * member.
+ * The type of the value is such that a cast or conversion to the type of + * the variable is possible. Otherwise, invoking the setter does nothing. * * @tparam Type Type of value to assign. - * @param handle An opaque pointer to an instance of the underlying type. + * @param instance An opaque instance of the underlying type. * @param value Parameter to use to set the underlying variable. * @return True in case of success, false otherwise. */ template - inline bool set(meta_handle handle, Type &&value) const { - return node->set(handle, meta_any{}, std::forward(value)); - } - - /** - * @brief Sets the i-th element of an array enclosed by a given meta type. - * - * It must be possible to cast the instance to the parent type of the meta - * data. Otherwise, invoking the setter results in an undefined - * behavior.
- * The type of the value must coincide exactly with that of the array type - * enclosed by the meta data. Otherwise, invoking the setter does nothing. - * - * @tparam Type Type of value to assign. - * @param handle An opaque pointer to an instance of the underlying type. - * @param index Position of the underlying element to set. - * @param value Parameter to use to set the underlying element. - * @return True in case of success, false otherwise. - */ - template - inline bool set(meta_handle handle, std::size_t index, Type &&value) const { - ENTT_ASSERT(index < node->type()->extent); - return node->set(handle, index, std::forward(value)); - } - - /** - * @brief Gets the value of the variable enclosed by a given meta type. - * - * It must be possible to cast the instance to the parent type of the meta - * data. Otherwise, invoking the getter results in an undefined behavior. - * - * @param handle An opaque pointer to an instance of the underlying type. - * @return A meta any containing the value of the underlying variable. - */ - inline meta_any get(meta_handle handle) const ENTT_NOEXCEPT { - return node->get(handle, meta_any{}); + bool set(meta_handle instance, Type &&value) const { + return node->set && node->set(std::move(instance), std::forward(value)); } /** - * @brief Gets the i-th element of an array enclosed by a given meta type. + * @brief Gets the value of a given variable. * - * It must be possible to cast the instance to the parent type of the meta - * data. Otherwise, invoking the getter results in an undefined behavior. + * It must be possible to cast the instance to the parent type of the data + * member. * - * @param handle An opaque pointer to an instance of the underlying type. - * @param index Position of the underlying element to get. - * @return A meta any containing the value of the underlying element. + * @param instance An opaque instance of the underlying type. + * @return A wrapper containing the value of the underlying variable. */ - inline meta_any get(meta_handle handle, std::size_t index) const ENTT_NOEXCEPT { - ENTT_ASSERT(index < node->type()->extent); - return node->get(handle, index); + [[nodiscard]] meta_any get(meta_handle instance) const { + return node->get(std::move(instance)); } /** - * @brief Iterates all the properties assigned to a meta data. - * @tparam Op Type of the function object to invoke. - * @param op A valid function object. + * @brief Returns the type accepted by the i-th setter. + * @param index Index of the setter of which to return the accepted type. + * @return The type accepted by the i-th setter. */ - template - inline std::enable_if_t, void> - prop(Op op) const ENTT_NOEXCEPT { - internal::iterate([op = std::move(op)](auto *curr) { - op(curr->meta()); - }, node->prop); - } + [[nodiscard]] inline meta_type arg(const size_type index) const ENTT_NOEXCEPT; /** - * @brief Returns the property associated with a given key. - * @tparam Key Type of key to use to search for a property. - * @param key The key to use to search for a property. - * @return The property associated with the given key, if any. + * @brief Returns a range to visit registered meta properties. + * @return An iterable range to visit registered meta properties. */ - template - inline std::enable_if_t, meta_prop> - prop(Key &&key) const ENTT_NOEXCEPT { - const auto *curr = internal::find_if([key = meta_any{std::forward(key)}](auto *candidate) { - return candidate->key() == key; - }, node->prop); - - return curr ? curr->meta() : meta_prop{}; + [[nodiscard]] meta_range prop() const ENTT_NOEXCEPT { + return node->prop; } /** - * @brief Returns true if a meta object is valid, false otherwise. - * @return True if the meta object is valid, false otherwise. + * @brief Lookup function for registered meta properties. + * @param key The key to use to search for a property. + * @return The registered meta property for the given key, if any. */ - inline explicit operator bool() const ENTT_NOEXCEPT { - return node; + [[nodiscard]] meta_prop prop(meta_any key) const { + for(auto curr: prop()) { + if(curr.key() == key) { + return curr; + } + } + + return nullptr; } /** - * @brief Checks if two meta objects refer to the same node. - * @param other The meta object with which to compare. - * @return True if the two meta objects refer to the same node, false - * otherwise. + * @brief Returns true if an object is valid, false otherwise. + * @return True if the object is valid, false otherwise. */ - inline bool operator==(const meta_data &other) const ENTT_NOEXCEPT { - return node == other.node; + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return !(node == nullptr); } private: - const internal::meta_data_node *node; + const node_type *node; }; - -/** - * @brief Checks if two meta objects refer to the same node. - * @param lhs A meta object, either valid or not. - * @param rhs A meta object, either valid or not. - * @return True if the two meta objects refer to the same node, false otherwise. - */ -inline bool operator!=(const meta_data &lhs, const meta_data &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); -} - - -/** - * @brief Meta function object. - * - * A meta function is an opaque container for a member function associated with - * a given type. - */ -class meta_func { - /*! @brief A meta factory is allowed to create meta objects. */ - template friend class meta_factory; - - inline meta_func(const internal::meta_func_node *curr) ENTT_NOEXCEPT - : node{curr} - {} - -public: +/*! @brief Opaque wrapper for member functions. */ +struct meta_func { + /*! @brief Node type. */ + using node_type = internal::meta_func_node; /*! @brief Unsigned integer type. */ - using size_type = typename internal::meta_func_node::size_type; + using size_type = typename node_type::size_type; - /*! @brief Default constructor. */ - inline meta_func() ENTT_NOEXCEPT - : node{nullptr} - {} + /*! @copydoc meta_prop::meta_prop */ + meta_func(const node_type *curr = nullptr) ENTT_NOEXCEPT + : node{curr} {} - /** - * @brief Returns the name assigned to a given meta function. - * @return The name assigned to the meta function. - */ - inline const char * name() const ENTT_NOEXCEPT { - return node->name; + /*! @copydoc meta_type::id */ + [[nodiscard]] id_type id() const ENTT_NOEXCEPT { + return node->id; } /** - * @brief Returns the meta type to which a meta function belongs. - * @return The meta type to which the meta function belongs. + * @brief Returns the number of arguments accepted by a member function. + * @return The number of arguments accepted by the member function. */ - inline meta_type parent() const ENTT_NOEXCEPT; + [[nodiscard]] size_type arity() const ENTT_NOEXCEPT { + return node->arity; + } /** - * @brief Returns the number of arguments accepted by a meta function. - * @return The number of arguments accepted by the meta function. + * @brief Indicates whether a member function is constant or not. + * @return True if the member function is constant, false otherwise. */ - inline size_type size() const ENTT_NOEXCEPT { - return node->size; + [[nodiscard]] bool is_const() const ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_const); } /** - * @brief Indicates whether a given meta function is constant or not. - * @return True if the meta function is constant, false otherwise. + * @brief Indicates whether a member function is static or not. + * @return True if the member function is static, false otherwise. */ - inline bool is_const() const ENTT_NOEXCEPT { - return node->is_const; + [[nodiscard]] bool is_static() const ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_static); } /** - * @brief Indicates whether a given meta function is static or not. - * - * A static meta function is such that it can be invoked using a null - * pointer as an instance. - * - * @return True if the meta function is static, false otherwise. + * @brief Returns the return type of a member function. + * @return The return type of the member function. */ - inline bool is_static() const ENTT_NOEXCEPT { - return node->is_static; - } + [[nodiscard]] inline meta_type ret() const ENTT_NOEXCEPT; /** - * @brief Returns the meta type of the return type of a meta function. - * @return The meta type of the return type of the meta function. + * @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. */ - inline meta_type ret() const ENTT_NOEXCEPT; + [[nodiscard]] inline meta_type arg(const size_type index) const ENTT_NOEXCEPT; /** - * @brief Returns the meta type of the i-th argument of a meta function. - * @param index The index of the argument of which to return the meta type. - * @return The meta type of the i-th argument of a meta function, if any. + * @brief Invokes the underlying function, if possible. + * + * To invoke a member function, the parameters must be such that a cast or + * conversion to the required types is possible. Otherwise, an empty and + * thus invalid wrapper is returned.
+ * It must be possible to cast the instance to the parent type of the member + * function. + * + * @param instance An opaque instance of the underlying type. + * @param args Parameters to use to invoke the function. + * @param sz Number of parameters to use to invoke the function. + * @return A wrapper containing the returned value, if any. */ - inline meta_type arg(size_type index) const ENTT_NOEXCEPT; + meta_any invoke(meta_handle instance, meta_any *const args, const size_type sz) const { + return sz == arity() ? node->invoke(std::move(instance), args) : meta_any{}; + } /** - * @brief Invokes the underlying function, if possible. + * @copybrief invoke * - * To invoke a meta function, the types of the parameters must coincide - * exactly with those required by the underlying function. Otherwise, an - * empty and then invalid container is returned.
- * It must be possible to cast the instance to the parent type of the meta - * function. Otherwise, invoking the underlying function results in an - * undefined behavior. + * @sa invoke * * @tparam Args Types of arguments to use to invoke the function. - * @param handle An opaque pointer to an instance of the underlying type. + * @param instance An opaque instance of the underlying type. * @param args Parameters to use to invoke the function. - * @return A meta any containing the returned value, if any. + * @return A wrapper containing the new instance, if any. */ template - meta_any invoke(meta_handle handle, Args &&... args) const { - std::array arguments{{std::forward(args)...}}; - meta_any any{}; - - if(sizeof...(Args) == size()) { - any = node->invoke(handle, arguments.data()); - } - - return any; + meta_any invoke(meta_handle instance, Args &&...args) const { + meta_any arguments[sizeof...(Args) + 1u]{std::forward(args)...}; + return invoke(std::move(instance), arguments, sizeof...(Args)); } - /** - * @brief Iterates all the properties assigned to a meta function. - * @tparam Op Type of the function object to invoke. - * @param op A valid function object. - */ - template - inline std::enable_if_t, void> - prop(Op op) const ENTT_NOEXCEPT { - internal::iterate([op = std::move(op)](auto *curr) { - op(curr->meta()); - }, node->prop); + /*! @copydoc meta_data::prop */ + [[nodiscard]] meta_range prop() const ENTT_NOEXCEPT { + return node->prop; } /** - * @brief Returns the property associated with a given key. - * @tparam Key Type of key to use to search for a property. + * @brief Lookup function for registered meta properties. * @param key The key to use to search for a property. - * @return The property associated with the given key, if any. + * @return The registered meta property for the given key, if any. */ - template - inline std::enable_if_t, meta_prop> - prop(Key &&key) const ENTT_NOEXCEPT { - const auto *curr = internal::find_if([key = meta_any{std::forward(key)}](auto *candidate) { - return candidate->key() == key; - }, node->prop); - - return curr ? curr->meta() : meta_prop{}; - } + [[nodiscard]] meta_prop prop(meta_any key) const { + for(auto curr: prop()) { + if(curr.key() == key) { + return curr; + } + } - /** - * @brief Returns true if a meta object is valid, false otherwise. - * @return True if the meta object is valid, false otherwise. - */ - inline explicit operator bool() const ENTT_NOEXCEPT { - return node; + return nullptr; } /** - * @brief Checks if two meta objects refer to the same node. - * @param other The meta object with which to compare. - * @return True if the two meta objects refer to the same node, false - * otherwise. + * @brief Returns true if an object is valid, false otherwise. + * @return True if the object is valid, false otherwise. */ - inline bool operator==(const meta_func &other) const ENTT_NOEXCEPT { - return node == other.node; + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return !(node == nullptr); } private: - const internal::meta_func_node *node; + const node_type *node; }; - -/** - * @brief Checks if two meta objects refer to the same node. - * @param lhs A meta object, either valid or not. - * @param rhs A meta object, either valid or not. - * @return True if the two meta objects refer to the same node, false otherwise. - */ -inline bool operator!=(const meta_func &lhs, const meta_func &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); -} - - -/** - * @brief Meta type object. - * - * A meta type is the starting point for accessing a reflected type, thus being - * able to work through it on real objects. - */ +/*! @brief Opaque wrapper for types. */ class meta_type { - /*! @brief A meta factory is allowed to create meta objects. */ - template friend class meta_factory; + template + [[nodiscard]] std::decay_t().*Member)> lookup(meta_any *const args, const typename internal::meta_type_node::size_type sz, Pred pred) const { + std::decay_t*Member)> candidate{}; + size_type extent{sz + 1u}; + bool ambiguous{}; + + for(auto *curr = (node->*Member); curr; curr = curr->next) { + if(pred(curr) && curr->arity == sz) { + size_type direct{}; + size_type ext{}; + + for(size_type next{}; next < sz && next == (direct + ext) && args[next]; ++next) { + const auto type = args[next].type(); + const auto other = curr->arg(next); + + if(const auto &info = other.info(); info == type.info()) { + ++direct; + } else { + ext += internal::find_by<&node_type::base>(info, type.node) + || internal::find_by<&node_type::conv>(info, type.node) + || (type.node->conversion_helper && other.node->conversion_helper); + } + } - /*! @brief A meta node is allowed to create meta objects. */ - template friend struct internal::meta_node; + if((direct + ext) == sz) { + if(ext < extent) { + candidate = curr; + extent = ext; + ambiguous = false; + } else if(ext == extent) { + ambiguous = true; + } + } + } + } - inline meta_type(const internal::meta_type_node *curr) ENTT_NOEXCEPT - : node{curr} - {} + return (candidate && !ambiguous) ? candidate : decltype(candidate){}; + } public: + /*! @brief Node type. */ + using node_type = internal::meta_type_node; + /*! @brief Node type. */ + using base_node_type = internal::meta_base_node; /*! @brief Unsigned integer type. */ - using size_type = typename internal::meta_type_node::size_type; + using size_type = typename node_type::size_type; - /*! @brief Default constructor. */ - inline meta_type() ENTT_NOEXCEPT - : node{nullptr} - {} + /*! @copydoc meta_prop::meta_prop */ + meta_type(const node_type *curr = nullptr) ENTT_NOEXCEPT + : node{curr} {} /** - * @brief Returns the name assigned to a given meta type. - * @return The name assigned to the meta type. + * @brief Constructs an instance from a given base node. + * @param curr The base node with which to construct the instance. */ - inline const char * name() const ENTT_NOEXCEPT { - return node->name; - } + meta_type(const base_node_type *curr) ENTT_NOEXCEPT + : node{curr ? curr->type : nullptr} {} /** - * @brief Indicates whether a given meta type refers to void or not. - * @return True if the underlying type is void, false otherwise. + * @brief Returns the type info object of the underlying type. + * @return The type info object of the underlying type. */ - inline bool is_void() const ENTT_NOEXCEPT { - return node->is_void; + [[nodiscard]] const type_info &info() const ENTT_NOEXCEPT { + return *node->info; } /** - * @brief Indicates whether a given meta type refers to an integral type or - * not. - * @return True if the underlying type is an integral type, false otherwise. + * @brief Returns the identifier assigned to a type. + * @return The identifier assigned to the type. */ - inline bool is_integral() const ENTT_NOEXCEPT { - return node->is_integral; + [[nodiscard]] id_type id() const ENTT_NOEXCEPT { + return node->id; } /** - * @brief Indicates whether a given meta type refers to a floating-point - * type or not. - * @return True if the underlying type is a floating-point type, false - * otherwise. + * @brief Returns the size of the underlying type if known. + * @return The size of the underlying type if known, 0 otherwise. */ - inline bool is_floating_point() const ENTT_NOEXCEPT { - return node->is_floating_point; + [[nodiscard]] size_type size_of() const ENTT_NOEXCEPT { + return node->size_of; } /** - * @brief Indicates whether a given meta type refers to an array type or - * not. - * @return True if the underlying type is an array type, false otherwise. + * @brief Checks whether a type refers to an arithmetic type or not. + * @return True if the underlying type is an arithmetic type, false + * otherwise. */ - inline bool is_array() const ENTT_NOEXCEPT { - return node->is_array; + [[nodiscard]] bool is_arithmetic() const ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_arithmetic); } /** - * @brief Indicates whether a given meta type refers to an enum or not. - * @return True if the underlying type is an enum, false otherwise. + * @brief Checks whether a type refers to an array type or not. + * @return True if the underlying type is an array type, false otherwise. */ - inline bool is_enum() const ENTT_NOEXCEPT { - return node->is_enum; + [[nodiscard]] bool is_array() const ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_array); } /** - * @brief Indicates whether a given meta type refers to an union or not. - * @return True if the underlying type is an union, false otherwise. + * @brief Checks whether a type refers to an enum or not. + * @return True if the underlying type is an enum, false otherwise. */ - inline bool is_union() const ENTT_NOEXCEPT { - return node->is_union; + [[nodiscard]] bool is_enum() const ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_enum); } /** - * @brief Indicates whether a given meta type refers to a class or not. + * @brief Checks whether a type refers to a class or not. * @return True if the underlying type is a class, false otherwise. */ - inline bool is_class() const ENTT_NOEXCEPT { - return node->is_class; + [[nodiscard]] bool is_class() const ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_class); } /** - * @brief Indicates whether a given meta type refers to a pointer or not. + * @brief Checks whether a type refers to a pointer or not. * @return True if the underlying type is a pointer, false otherwise. */ - inline bool is_pointer() const ENTT_NOEXCEPT { - return node->is_pointer; + [[nodiscard]] bool is_pointer() const ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_pointer); } /** - * @brief Indicates whether a given meta type refers to a function type or - * not. - * @return True if the underlying type is a function, false otherwise. + * @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. */ - inline bool is_function() const ENTT_NOEXCEPT { - return node->is_function; + [[nodiscard]] meta_type remove_pointer() const ENTT_NOEXCEPT { + return node->remove_pointer(); } /** - * @brief Indicates whether a given meta type refers to a pointer to data - * member or not. - * @return True if the underlying type is a pointer to data member, false + * @brief Checks whether a type is a pointer-like type or not. + * @return True if the underlying type is a pointer-like one, false * otherwise. */ - inline bool is_member_object_pointer() const ENTT_NOEXCEPT { - return node->is_member_object_pointer; + [[nodiscard]] bool is_pointer_like() const ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_meta_pointer_like); } /** - * @brief Indicates whether a given meta type refers to a pointer to member - * function or not. - * @return True if the underlying type is a pointer to member function, - * false otherwise. + * @brief Checks whether a type refers to a sequence container or not. + * @return True if the type is a sequence container, false otherwise. */ - inline bool is_member_function_pointer() const ENTT_NOEXCEPT { - return node->is_member_function_pointer; + [[nodiscard]] bool is_sequence_container() const ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_meta_sequence_container); } /** - * @brief If a given meta type refers to an array type, provides the number - * of elements of the array. - * @return The number of elements of the array if the underlying type is an - * array type, 0 otherwise. + * @brief Checks whether a type refers to an associative container or not. + * @return True if the type is an associative container, false otherwise. */ - inline size_type extent() const ENTT_NOEXCEPT { - return node->extent; + [[nodiscard]] bool is_associative_container() const ENTT_NOEXCEPT { + return !!(node->traits & internal::meta_traits::is_meta_associative_container); } /** - * @brief Provides the meta type for which the pointer is defined. - * @return The meta type for which the pointer is defined or this meta type - * if it doesn't refer to a pointer type. + * @brief Checks whether a type refers to a recognized class template + * specialization or not. + * @return True if the type is a recognized class template specialization, + * false otherwise. */ - inline meta_type remove_pointer() const ENTT_NOEXCEPT { - return node->remove_pointer(); + [[nodiscard]] bool is_template_specialization() const ENTT_NOEXCEPT { + return (node->templ != nullptr); } /** - * @brief Iterates all the meta base of a meta type. - * - * Iteratively returns **all** the base classes of the given type. - * - * @tparam Op Type of the function object to invoke. - * @param op A valid function object. + * @brief Returns the number of template arguments. + * @return The number of template arguments. */ - template - inline void base(Op op) const ENTT_NOEXCEPT { - internal::iterate<&internal::meta_type_node::base>([op = std::move(op)](auto *curr) { - op(curr->meta()); - }, node); + [[nodiscard]] size_type template_arity() const ENTT_NOEXCEPT { + return node->templ ? node->templ->arity : size_type{}; } /** - * @brief Returns the meta base associated with a given name. + * @brief Returns a tag for the class template of the underlying type. * - * Searches recursively among **all** the base classes of the given type. + * @sa meta_class_template_tag * - * @param str The name to use to search for a meta base. - * @return The meta base associated with the given name, if any. + * @return The tag for the class template of the underlying type. */ - inline meta_base base(const char *str) const ENTT_NOEXCEPT { - const auto *curr = internal::find_if<&internal::meta_type_node::base>([name = hashed_string{str}](auto *candidate) { - return candidate->type()->name == name; - }, node); - - return curr ? curr->meta() : meta_base{}; + [[nodiscard]] inline meta_type template_type() const ENTT_NOEXCEPT { + return node->templ ? node->templ->type : meta_type{}; } /** - * @brief Iterates all the meta conversion functions of a meta type. - * - * Iteratively returns **all** the meta conversion functions of the given - * type. - * - * @tparam Op Type of the function object to invoke. - * @param op A valid function object. + * @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. */ - template - inline void conv(Op op) const ENTT_NOEXCEPT { - internal::iterate<&internal::meta_type_node::conv>([op = std::move(op)](auto *curr) { - op(curr->meta()); - }, node); + [[nodiscard]] inline meta_type template_arg(const size_type index) const ENTT_NOEXCEPT { + return index < template_arity() ? node->templ->arg(index) : meta_type{}; } /** - * @brief Returns the meta conversion function associated with a given type. - * - * Searches recursively among **all** the conversion functions of the given - * type. - * - * @tparam Type The type to use to search for a meta conversion function. - * @return The meta conversion function associated with the given type, if - * any. + * @brief Returns a range to visit registered top-level base meta types. + * @return An iterable range to visit registered top-level base meta types. */ - template - inline meta_conv conv() const ENTT_NOEXCEPT { - const auto *curr = internal::find_if<&internal::meta_type_node::conv>([type = internal::meta_info::resolve()](auto *candidate) { - return candidate->type() == type; - }, node); + [[nodiscard]] meta_range base() const ENTT_NOEXCEPT { + return node->base; + } - return curr ? curr->meta() : meta_conv{}; + /** + * @brief Lookup function for registered base meta types. + * @param id Unique identifier. + * @return The registered base meta type for the given identifier, if any. + */ + [[nodiscard]] meta_type base(const id_type id) const { + return internal::find_by<&node_type::base>(id, node); } /** - * @brief Iterates all the meta constructors of a meta type. - * @tparam Op Type of the function object to invoke. - * @param op A valid function object. + * @brief Returns a range to visit registered top-level meta data. + * @return An iterable range to visit registered top-level meta data. */ - template - inline void ctor(Op op) const ENTT_NOEXCEPT { - internal::iterate([op = std::move(op)](auto *curr) { - op(curr->meta()); - }, node->ctor); + [[nodiscard]] meta_range data() const ENTT_NOEXCEPT { + return node->data; } /** - * @brief Returns the meta constructor that accepts a given list of types of - * arguments. - * @return The requested meta constructor, if any. + * @brief Lookup function for registered meta data. + * + * Registered meta data of base classes will also be visited. + * + * @param id Unique identifier. + * @return The registered meta data for the given identifier, if any. */ - template - inline meta_ctor ctor() const ENTT_NOEXCEPT { - const auto *curr = internal::ctor(std::make_index_sequence{}, node); - return curr ? curr->meta() : meta_ctor{}; + [[nodiscard]] meta_data data(const id_type id) const { + return internal::find_by<&node_type::data>(id, node); } /** - * @brief Returns the meta destructor associated with a given type. - * @return The meta destructor associated with the given type, if any. + * @brief Returns a range to visit registered top-level functions. + * @return An iterable range to visit registered top-level functions. */ - inline meta_dtor dtor() const ENTT_NOEXCEPT { - return node->dtor ? node->dtor->meta() : meta_dtor{}; + [[nodiscard]] meta_range func() const ENTT_NOEXCEPT { + return node->func; } /** - * @brief Iterates all the meta data of a meta type. + * @brief Lookup function for registered meta functions. * - * Iteratively returns **all** the meta data of the given type. This means - * that the meta data of the base classes will also be returned, if any. + * Registered meta functions of base classes will also be visited.
+ * In case of overloaded functions, the first one with the required + * identifier will be returned. * - * @tparam Op Type of the function object to invoke. - * @param op A valid function object. + * @param id Unique identifier. + * @return The registered meta function for the given identifier, if any. */ - template - inline void data(Op op) const ENTT_NOEXCEPT { - internal::iterate<&internal::meta_type_node::data>([op = std::move(op)](auto *curr) { - op(curr->meta()); - }, node); + [[nodiscard]] meta_func func(const id_type id) const { + return internal::find_by<&node_type::func>(id, node); } /** - * @brief Returns the meta data associated with a given name. + * @brief Creates an instance of the underlying type, if possible. * - * Searches recursively among **all** the meta data of the given type. This - * means that the meta data of the base classes will also be inspected, if - * any. + * Parameters are such that a cast or conversion to the required types is + * possible. Otherwise, an empty and thus invalid wrapper is returned.
+ * If suitable, the implicitly generated default constructor is used. * - * @param str The name to use to search for a meta data. - * @return The meta data associated with the given name, if any. + * @param args Parameters to use to construct the instance. + * @param sz Number of parameters to use to construct the instance. + * @return A wrapper containing the new instance, if any. */ - inline meta_data data(const char *str) const ENTT_NOEXCEPT { - const auto *curr = internal::find_if<&internal::meta_type_node::data>([name = hashed_string{str}](auto *candidate) { - return candidate->name == name; - }, node); - - return curr ? curr->meta() : meta_data{}; + [[nodiscard]] meta_any construct(meta_any *const args, const size_type sz) const { + const auto *candidate = lookup<&node_type::ctor>(args, sz, [](const auto *) { return true; }); + return candidate ? candidate->invoke(args) : ((!sz && node->default_constructor) ? node->default_constructor() : meta_any{}); } /** - * @brief Iterates all the meta functions of a meta type. + * @copybrief construct * - * Iteratively returns **all** the meta functions of the given type. This - * means that the meta functions of the base classes will also be returned, - * if any. + * @sa construct * - * @tparam Op Type of the function object to invoke. - * @param op A valid function object. + * @tparam Args Types of arguments to use to construct the instance. + * @param args Parameters to use to construct the instance. + * @return A wrapper containing the new instance, if any. */ - template - inline void func(Op op) const ENTT_NOEXCEPT { - internal::iterate<&internal::meta_type_node::func>([op = std::move(op)](auto *curr) { - op(curr->meta()); - }, node); + template + [[nodiscard]] meta_any construct(Args &&...args) const { + meta_any arguments[sizeof...(Args) + 1u]{std::forward(args)...}; + return construct(arguments, sizeof...(Args)); } /** - * @brief Returns the meta function associated with a given name. + * @brief Invokes a function given an identifier, if possible. + * + * It must be possible to cast the instance to the parent type of the member + * function. * - * Searches recursively among **all** the meta functions of the given type. - * This means that the meta functions of the base classes will also be - * inspected, if any. + * @sa meta_func::invoke * - * @param str The name to use to search for a meta function. - * @return The meta function associated with the given name, if any. + * @param id Unique identifier. + * @param instance An opaque instance of the underlying type. + * @param args Parameters to use to invoke the function. + * @param sz Number of parameters to use to invoke the function. + * @return A wrapper containing the returned value, if any. */ - inline meta_func func(const char *str) const ENTT_NOEXCEPT { - const auto *curr = internal::find_if<&internal::meta_type_node::func>([name = hashed_string{str}](auto *candidate) { - return candidate->name == name; - }, node); + meta_any invoke(const id_type id, meta_handle instance, meta_any *const args, const size_type sz) const { + const auto *candidate = lookup<&node_type::func>(args, sz, [id](const auto *curr) { return curr->id == id; }); + + for(auto it = base().begin(), last = base().end(); it != last && !candidate; ++it) { + candidate = it->lookup<&node_type::func>(args, sz, [id](const auto *curr) { return curr->id == id; }); + } - return curr ? curr->meta() : meta_func{}; + return candidate ? candidate->invoke(std::move(instance), args) : meta_any{}; } /** - * @brief Creates an instance of the underlying type, if possible. + * @copybrief invoke * - * To create a valid instance, the types of the parameters must coincide - * exactly with those required by the underlying meta constructor. - * Otherwise, an empty and then invalid container is returned. + * @sa invoke * - * @tparam Args Types of arguments to use to construct the instance. - * @param args Parameters to use to construct the instance. - * @return A meta any containing the new instance, if any. + * @param id Unique identifier. + * @tparam Args Types of arguments to use to invoke the function. + * @param instance An opaque instance of the underlying type. + * @param args Parameters to use to invoke the function. + * @return A wrapper containing the new instance, if any. */ template - meta_any construct(Args &&... args) const { - std::array arguments{{std::forward(args)...}}; - meta_any any{}; - - internal::iterate<&internal::meta_type_node::ctor>([data = arguments.data(), &any](auto *curr) -> bool { - any = curr->invoke(data); - return static_cast(any); - }, node); - - return any; + meta_any invoke(const id_type id, meta_handle instance, Args &&...args) const { + meta_any arguments[sizeof...(Args) + 1u]{std::forward(args)...}; + return invoke(id, std::move(instance), arguments, sizeof...(Args)); } /** - * @brief Destroys an instance of the underlying type. + * @brief Sets the value of a given variable. * - * It must be possible to cast the instance to the underlying type. - * Otherwise, invoking the meta destructor results in an undefined behavior. + * It must be possible to cast the instance to the parent type of the data + * member.
+ * The type of the value is such that a cast or conversion to the type of + * the variable is possible. Otherwise, invoking the setter does nothing. * - * @param handle An opaque pointer to an instance of the underlying type. + * @tparam Type Type of value to assign. + * @param id Unique identifier. + * @param instance An opaque instance of the underlying type. + * @param value Parameter to use to set the underlying variable. * @return True in case of success, false otherwise. */ - inline bool destroy(meta_handle handle) const { - return node->dtor ? node->dtor->invoke(handle) : node->destroy(handle); + template + bool set(const id_type id, meta_handle instance, Type &&value) const { + const auto candidate = data(id); + return candidate && candidate.set(std::move(instance), std::forward(value)); } /** - * @brief Iterates all the properties assigned to a meta type. + * @brief Gets the value of a given variable. * - * Iteratively returns **all** the properties of the given type. This means - * that the properties of the base classes will also be returned, if any. + * It must be possible to cast the instance to the parent type of the data + * member. * - * @tparam Op Type of the function object to invoke. - * @param op A valid function object. + * @param id Unique identifier. + * @param instance An opaque instance of the underlying type. + * @return A wrapper containing the value of the underlying variable. + */ + [[nodiscard]] meta_any get(const id_type id, meta_handle instance) const { + const auto candidate = data(id); + return candidate ? candidate.get(std::move(instance)) : meta_any{}; + } + + /** + * @brief Returns a range to visit registered top-level meta properties. + * @return An iterable range to visit registered top-level meta properties. */ - template - inline std::enable_if_t, void> - prop(Op op) const ENTT_NOEXCEPT { - internal::iterate<&internal::meta_type_node::prop>([op = std::move(op)](auto *curr) { - op(curr->meta()); - }, node); + [[nodiscard]] meta_range prop() const ENTT_NOEXCEPT { + return node->prop; } /** - * @brief Returns the property associated with a given key. + * @brief Lookup function for meta properties. * - * Searches recursively among **all** the properties of the given type. This - * means that the properties of the base classes will also be inspected, if - * any. + * Properties of base classes are also visited. * - * @tparam Key Type of key to use to search for a property. * @param key The key to use to search for a property. - * @return The property associated with the given key, if any. + * @return The registered meta property for the given key, if any. */ - template - inline std::enable_if_t, meta_prop> - prop(Key &&key) const ENTT_NOEXCEPT { - const auto *curr = internal::find_if<&internal::meta_type_node::prop>([key = meta_any{std::forward(key)}](auto *candidate) { - return candidate->key() == key; - }, node); - - return curr ? curr->meta() : meta_prop{}; + [[nodiscard]] meta_prop prop(meta_any key) const { + return internal::find_by<&internal::meta_type_node::prop>(key, node); } /** - * @brief Returns true if a meta object is valid, false otherwise. - * @return True if the meta object is valid, false otherwise. + * @brief Returns true if an object is valid, false otherwise. + * @return True if the object is valid, false otherwise. */ - inline explicit operator bool() const ENTT_NOEXCEPT { - return node; + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return !(node == nullptr); } /** - * @brief Checks if two meta objects refer to the same node. - * @param other The meta object with which to compare. - * @return True if the two meta objects refer to the same node, false - * otherwise. + * @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. */ - inline bool operator==(const meta_type &other) const ENTT_NOEXCEPT { - return node == other.node; + [[nodiscard]] bool operator==(const meta_type &other) const ENTT_NOEXCEPT { + return (!node && !other.node) || (node && other.node && *node->info == *other.node->info); } private: - const internal::meta_type_node *node; + const node_type *node; }; - /** - * @brief Checks if two meta objects refer to the same node. - * @param lhs A meta object, either valid or not. - * @param rhs A meta object, either valid or not. - * @return True if the two meta objects refer to the same node, false otherwise. + * @brief Checks if two objects refer to the same type. + * @param lhs An object, either valid or not. + * @param rhs An object, either valid or not. + * @return False if the objects refer to the same node, true otherwise. */ -inline bool operator!=(const meta_type &lhs, const meta_type &rhs) ENTT_NOEXCEPT { +[[nodiscard]] inline bool operator!=(const meta_type &lhs, const meta_type &rhs) ENTT_NOEXCEPT { return !(lhs == rhs); } - -inline meta_type meta_any::type() const ENTT_NOEXCEPT { - return node ? node->meta() : meta_type{}; +[[nodiscard]] inline meta_type meta_any::type() const ENTT_NOEXCEPT { + return node; } - -inline meta_type meta_handle::type() const ENTT_NOEXCEPT { - return node ? node->meta() : meta_type{}; +template +meta_any meta_any::invoke(const id_type id, Args &&...args) const { + return type().invoke(id, *this, std::forward(args)...); } - -inline meta_type meta_base::parent() const ENTT_NOEXCEPT { - return node->parent->meta(); +template +meta_any meta_any::invoke(const id_type id, Args &&...args) { + return type().invoke(id, *this, std::forward(args)...); } - -inline meta_type meta_base::type() const ENTT_NOEXCEPT { - return node->type()->meta(); +template +bool meta_any::set(const id_type id, Type &&value) { + return type().set(id, *this, std::forward(value)); } - -inline meta_type meta_conv::parent() const ENTT_NOEXCEPT { - return node->parent->meta(); +[[nodiscard]] inline meta_any meta_any::get(const id_type id) const { + return type().get(id, *this); } - -inline meta_type meta_conv::type() const ENTT_NOEXCEPT { - return node->type()->meta(); +[[nodiscard]] inline meta_any meta_any::get(const id_type id) { + return type().get(id, *this); } +[[nodiscard]] inline meta_any meta_any::allow_cast(const meta_type &type) const { + if(const auto &info = type.info(); node && *node->info == info) { + return as_ref(); + } else if(node) { + for(auto *it = node->conv; it; it = it->next) { + if(*it->type->info == info) { + return it->conv(*this); + } + } -inline meta_type meta_ctor::parent() const ENTT_NOEXCEPT { - return node->parent->meta(); -} - + if(node->conversion_helper && (type.is_arithmetic() || type.is_enum())) { + // exploits the fact that arithmetic types and enums are also default constructible + auto other = type.construct(); + ENTT_ASSERT(other.node->conversion_helper, "Conversion helper not found"); + const auto value = node->conversion_helper(nullptr, storage.data()); + other.node->conversion_helper(other.storage.data(), &value); + return other; + } -inline meta_type meta_ctor::arg(size_type index) const ENTT_NOEXCEPT { - return index < size() ? node->arg(index)->meta() : meta_type{}; -} + for(auto *it = node->base; it; it = it->next) { + const auto as_const = it->cast(as_ref()); + if(auto other = as_const.allow_cast(type); other) { + return other; + } + } + } -inline meta_type meta_dtor::parent() const ENTT_NOEXCEPT { - return node->parent->meta(); + return {}; } - -inline meta_type meta_data::parent() const ENTT_NOEXCEPT { - return node->parent->meta(); +inline bool meta_any::assign(const meta_any &other) { + auto value = other.allow_cast(node); + return value && storage.assign(std::move(value.storage)); } +inline bool meta_any::assign(meta_any &&other) { + if(*node->info == *other.node->info) { + return storage.assign(std::move(other.storage)); + } -inline meta_type meta_data::type() const ENTT_NOEXCEPT { - return node->type()->meta(); + return assign(std::as_const(other)); } - -inline meta_type meta_func::parent() const ENTT_NOEXCEPT { - return node->parent->meta(); +[[nodiscard]] inline meta_type meta_data::type() const ENTT_NOEXCEPT { + return node->type; } - -inline meta_type meta_func::ret() const ENTT_NOEXCEPT { - return node->ret()->meta(); +[[nodiscard]] inline meta_type meta_func::ret() const ENTT_NOEXCEPT { + return node->ret; } - -inline meta_type meta_func::arg(size_type index) const ENTT_NOEXCEPT { - return index < size() ? node->arg(index)->meta() : meta_type{}; +[[nodiscard]] inline meta_type meta_data::arg(const size_type index) const ENTT_NOEXCEPT { + return index < arity() ? node->arg(index) : meta_type{}; } +[[nodiscard]] inline meta_type meta_func::arg(const size_type index) const ENTT_NOEXCEPT { + return index < arity() ? node->arg(index) : meta_type{}; +} /** * @cond TURN_OFF_DOXYGEN * Internal details not to be documented. */ +class meta_sequence_container::meta_iterator final { + friend class meta_sequence_container; -namespace internal { + using deref_fn_type = meta_any(const any &, const std::ptrdiff_t); - -template -struct meta_function_helper; - - -template -struct meta_function_helper { - using return_type = Ret; - using args_type = std::tuple; - - template - using arg_type = std::decay_t>; - - static constexpr auto size = sizeof...(Args); - - inline static auto arg(typename internal::meta_func_node::size_type index) { - return std::array{{meta_info::resolve()...}}[index]; + template + static meta_any deref_fn(const any &value, const std::ptrdiff_t pos) { + return meta_any{std::in_place_type::reference>, any_cast(value)[pos]}; } -}; +public: + using difference_type = std::ptrdiff_t; + using value_type = meta_any; + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; -template -struct meta_function_helper, std::bool_constant>: meta_function_helper { - using class_type = Class; - static constexpr auto is_const = Const; - static constexpr auto is_static = Static; -}; + meta_iterator() ENTT_NOEXCEPT + : deref{}, + offset{}, + handle{} {} + template + explicit meta_iterator(Type &cont, const difference_type init) ENTT_NOEXCEPT + : deref{&deref_fn}, + offset{init}, + handle{cont.begin()} {} -template -constexpr meta_function_helper, std::bool_constant> -to_meta_function_helper(Ret(Class:: *)(Args...)); + meta_iterator &operator++() ENTT_NOEXCEPT { + return ++offset, *this; + } + meta_iterator operator++(int value) ENTT_NOEXCEPT { + meta_iterator orig = *this; + offset += ++value; + return orig; + } -template -constexpr meta_function_helper, std::bool_constant> -to_meta_function_helper(Ret(Class:: *)(Args...) const); + meta_iterator &operator--() ENTT_NOEXCEPT { + return --offset, *this; + } + meta_iterator operator--(int value) ENTT_NOEXCEPT { + meta_iterator orig = *this; + offset -= ++value; + return orig; + } -template -constexpr meta_function_helper, std::bool_constant> -to_meta_function_helper(Ret(*)(Args...)); + [[nodiscard]] reference operator*() const { + return deref(handle, offset); + } + [[nodiscard]] pointer operator->() const { + return operator*(); + } -template -struct meta_function_helper>: decltype(to_meta_function_helper(Func)) {}; + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return static_cast(handle); + } + [[nodiscard]] bool operator==(const meta_iterator &other) const ENTT_NOEXCEPT { + return offset == other.offset; + } -template -inline bool destroy([[maybe_unused]] meta_handle handle) { - bool accepted = false; + [[nodiscard]] bool operator!=(const meta_iterator &other) const ENTT_NOEXCEPT { + return !(*this == other); + } - if constexpr(std::is_object_v && !std::is_array_v) { - accepted = (handle.type() == meta_info::resolve()->meta()); +private: + deref_fn_type *deref; + difference_type offset; + any handle; +}; - if(accepted) { - static_cast(handle.data())->~Type(); +class meta_associative_container::meta_iterator final { + enum class operation : std::uint8_t { + incr, + deref + }; + + using vtable_type = void(const operation, const any &, std::pair *); + + template + static void basic_vtable(const operation op, const any &value, std::pair *other) { + switch(op) { + case operation::incr: + ++any_cast(const_cast(value)); + break; + case operation::deref: + const auto &it = any_cast(value); + if constexpr(KeyOnly) { + other->first.emplace(*it); + } else { + other->first.emplacefirst))>(it->first); + other->second.emplacesecond))>(it->second); + } + break; } } - return accepted; -} - - -template -inline meta_any construct(meta_any * const args, std::index_sequence) { - [[maybe_unused]] std::array can_cast{{(args+Indexes)->can_cast>>()...}}; - [[maybe_unused]] std::array can_convert{{(std::get(can_cast) ? false : (args+Indexes)->can_convert>>())...}}; - meta_any any{}; - - if(((std::get(can_cast) || std::get(can_convert)) && ...)) { - ((std::get(can_convert) ? void((args+Indexes)->convert>>()) : void()), ...); - any = Type{(args+Indexes)->cast>>()...}; +public: + using difference_type = std::ptrdiff_t; + using value_type = std::pair; + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + meta_iterator() ENTT_NOEXCEPT + : vtable{}, + handle{} {} + + template + meta_iterator(std::integral_constant, It iter) ENTT_NOEXCEPT + : vtable{&basic_vtable}, + handle{std::move(iter)} {} + + meta_iterator &operator++() ENTT_NOEXCEPT { + vtable(operation::incr, handle, nullptr); + return *this; } - return any; -} - - -template -bool setter([[maybe_unused]] meta_handle handle, [[maybe_unused]] meta_any index, [[maybe_unused]] meta_any value) { - bool accepted = false; - - if constexpr(!Const) { - if constexpr(std::is_function_v> || std::is_member_function_pointer_v) { - using helper_type = meta_function_helper>; - using data_type = std::decay_t, typename helper_type::args_type>>; - static_assert(std::is_invocable_v); - accepted = value.can_cast() || value.convert(); - auto *clazz = handle.try_cast(); + meta_iterator operator++(int) ENTT_NOEXCEPT { + meta_iterator orig = *this; + return ++(*this), orig; + } - if(accepted && clazz) { - std::invoke(Data, clazz, value.cast()); - } - } else if constexpr(std::is_member_object_pointer_v) { - using data_type = std::remove_cv_t().*Data)>>; - static_assert(std::is_invocable_v); - auto *clazz = handle.try_cast(); + [[nodiscard]] reference operator*() const { + reference other; + vtable(operation::deref, handle, &other); + return other; + } - if constexpr(std::is_array_v) { - using underlying_type = std::remove_extent_t; - accepted = index.can_cast() && (value.can_cast() || value.convert()); + [[nodiscard]] pointer operator->() const { + return operator*(); + } - if(accepted && clazz) { - std::invoke(Data, clazz)[index.cast()] = value.cast(); - } - } else { - accepted = value.can_cast() || value.convert(); + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return static_cast(handle); + } - if(accepted && clazz) { - std::invoke(Data, clazz) = value.cast(); - } - } - } else { - static_assert(std::is_pointer_v); - using data_type = std::remove_cv_t>; + [[nodiscard]] bool operator==(const meta_iterator &other) const ENTT_NOEXCEPT { + return handle == other.handle; + } - if constexpr(std::is_array_v) { - using underlying_type = std::remove_extent_t; - accepted = index.can_cast() && (value.can_cast() || value.convert()); + [[nodiscard]] bool operator!=(const meta_iterator &other) const ENTT_NOEXCEPT { + return !(*this == other); + } - if(accepted) { - (*Data)[index.cast()] = value.cast(); - } - } else { - accepted = value.can_cast() || value.convert(); +private: + vtable_type *vtable; + any handle; +}; - if(accepted) { - *Data = value.cast(); - } - } - } - } +/** + * Internal details not to be documented. + * @endcond + */ - return accepted; +/** + * @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 ENTT_NOEXCEPT { + return value_type_node; } +/** + * @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 ENTT_NOEXCEPT { + return size_fn(storage); +} -template -inline meta_any getter([[maybe_unused]] meta_handle handle, [[maybe_unused]] meta_any index) { - if constexpr(std::is_function_v> || std::is_member_function_pointer_v) { - static_assert(std::is_invocable_v); - auto *clazz = handle.try_cast(); - return clazz ? std::invoke(Data, clazz) : meta_any{}; - } else if constexpr(std::is_member_object_pointer_v) { - using data_type = std::remove_cv_t().*Data)>>; - static_assert(std::is_invocable_v); - auto *clazz = handle.try_cast(); - - if constexpr(std::is_array_v) { - return (clazz && index.can_cast()) ? std::invoke(Data, clazz)[index.cast()] : meta_any{}; - } else { - return clazz ? std::invoke(Data, clazz) : meta_any{}; - } - } else { - static_assert(std::is_pointer_v); +/** + * @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 resize_fn(storage, sz); +} - if constexpr(std::is_array_v>) { - return index.can_cast() ? (*Data)[index.cast()] : meta_any{}; - } else { - return *Data; - } - } +/** + * @brief Clears the content of a container. + * @return True in case of success, false otherwise. + */ +inline bool meta_sequence_container::clear() { + return resize_fn(storage, 0u); } +/** + * @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 iter_fn(storage, false); +} -template -std::enable_if_t>, meta_any> -invoke(const meta_handle &, meta_any *args, std::index_sequence) { - using helper_type = meta_function_helper>; - meta_any any{}; +/** + * @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 iter_fn(storage, true); +} - if((((args+Indexes)->can_cast>() - || (args+Indexes)->convert>()) && ...)) - { - if constexpr(std::is_void_v) { - std::invoke(Func, (args+Indexes)->cast>()...); - } else { - any = meta_any{std::invoke(Func, (args+Indexes)->cast>()...)}; - } - } +/** + * @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(iterator it, meta_any value) { + return insert_fn(storage, it.offset, value); +} - return any; +/** + * @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(iterator it) { + return erase_fn(storage, it.offset); } +/** + * @brief Returns a reference to the element at a given location of a container + * (no bounds checking is performed). + * @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; +} -template -std::enable_if_t, meta_any> -invoke(meta_handle &handle, meta_any *args, std::index_sequence) { - using helper_type = meta_function_helper>; - static_assert(std::is_base_of_v); - auto *clazz = handle.try_cast(); - meta_any any{}; +/** + * @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 ENTT_NOEXCEPT { + return static_cast(storage); +} - if(clazz && (((args+Indexes)->can_cast>() - || (args+Indexes)->convert>()) && ...)) - { - if constexpr(std::is_void_v) { - std::invoke(Member, clazz, (args+Indexes)->cast>()...); - } else { - any = meta_any{std::invoke(Member, clazz, (args+Indexes)->cast>()...)}; - } - } +/** + * @brief Returns true if a container is also key-only, false otherwise. + * @return True if the associative container is also key-only, false otherwise. + */ +[[nodiscard]] inline bool meta_associative_container::key_only() const ENTT_NOEXCEPT { + return key_only_container; +} - return any; +/** + * @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 ENTT_NOEXCEPT { + return key_type_node; } +/** + * @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 ENTT_NOEXCEPT { + return mapped_type_node; +} -template -meta_type_node * meta_node::resolve() ENTT_NOEXCEPT { - if(!type) { - static meta_type_node node{ - {}, - nullptr, - nullptr, - std::is_void_v, - std::is_integral_v, - std::is_floating_point_v, - std::is_array_v, - std::is_enum_v, - std::is_union_v, - std::is_class_v, - std::is_pointer_v, - std::is_function_v, - std::is_member_object_pointer_v, - std::is_member_function_pointer_v, - std::extent_v, - []() -> meta_type { - return internal::meta_info>::resolve(); - }, - &destroy, - []() -> meta_type { - return &node; - } - }; +/*! @copydoc meta_sequence_container::value_type */ +[[nodiscard]] inline meta_type meta_associative_container::value_type() const ENTT_NOEXCEPT { + return value_type_node; +} - type = &node; - } +/*! @copydoc meta_sequence_container::size */ +[[nodiscard]] inline meta_associative_container::size_type meta_associative_container::size() const ENTT_NOEXCEPT { + return size_fn(storage); +} - return type; +/*! @copydoc meta_sequence_container::clear */ +inline bool meta_associative_container::clear() { + return clear_fn(storage); } +/*! @copydoc meta_sequence_container::begin */ +[[nodiscard]] inline meta_associative_container::iterator meta_associative_container::begin() { + return iter_fn(storage, false); +} +/*! @copydoc meta_sequence_container::end */ +[[nodiscard]] inline meta_associative_container::iterator meta_associative_container::end() { + return iter_fn(storage, true); } +/** + * @brief Inserts an element (a key/value pair) into a container. + * @param key The key of the element to insert. + * @param value The value of the element to insert. + * @return A bool denoting whether the insertion took place. + */ +inline bool meta_associative_container::insert(meta_any key, meta_any value = {}) { + return insert_fn(storage, key, value); +} /** - * Internal details not to be documented. - * @endcond TURN_OFF_DOXYGEN + * @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 bool meta_associative_container::erase(meta_any key) { + return erase_fn(storage, key); +} +/** + * @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 find_fn(storage, key); +} +/** + * @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 ENTT_NOEXCEPT { + return static_cast(storage); } +} // namespace entt -#endif // ENTT_META_META_HPP +#endif diff --git a/modules/entt/src/entt/meta/node.hpp b/modules/entt/src/entt/meta/node.hpp new file mode 100644 index 0000000..813727d --- /dev/null +++ b/modules/entt/src/entt/meta/node.hpp @@ -0,0 +1,237 @@ +#ifndef ENTT_META_NODE_HPP +#define ENTT_META_NODE_HPP + +#include +#include +#include +#include "../config/config.h" +#include "../core/attribute.h" +#include "../core/enum.hpp" +#include "../core/fwd.hpp" +#include "../core/type_info.hpp" +#include "../core/type_traits.hpp" +#include "type_traits.hpp" + +namespace entt { + +class meta_any; +class meta_type; +struct meta_handle; + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +enum class meta_traits : std::uint32_t { + is_none = 0x0000, + is_const = 0x0001, + is_static = 0x0002, + is_arithmetic = 0x0004, + is_array = 0x0008, + is_enum = 0x0010, + is_class = 0x0020, + is_pointer = 0x0040, + is_meta_pointer_like = 0x0080, + is_meta_sequence_container = 0x0100, + is_meta_associative_container = 0x0200, + _entt_enum_as_bitmask +}; + +struct meta_type_node; + +struct meta_prop_node { + meta_prop_node *next; + const meta_any &id; + meta_any &value; +}; + +struct meta_base_node { + meta_base_node *next; + meta_type_node *const type; + meta_any (*const cast)(meta_any) ENTT_NOEXCEPT; +}; + +struct meta_conv_node { + meta_conv_node *next; + meta_type_node *const type; + meta_any (*const conv)(const meta_any &); +}; + +struct meta_ctor_node { + using size_type = std::size_t; + meta_ctor_node *next; + const size_type arity; + meta_type (*const arg)(const size_type) ENTT_NOEXCEPT; + meta_any (*const invoke)(meta_any *const); +}; + +struct meta_data_node { + using size_type = std::size_t; + id_type id; + const meta_traits traits; + meta_data_node *next; + meta_prop_node *prop; + const size_type arity; + meta_type_node *const type; + meta_type (*const arg)(const size_type) ENTT_NOEXCEPT; + bool (*const set)(meta_handle, meta_any); + meta_any (*const get)(meta_handle); +}; + +struct meta_func_node { + using size_type = std::size_t; + id_type id; + const meta_traits traits; + meta_func_node *next; + meta_prop_node *prop; + const size_type arity; + meta_type_node *const ret; + meta_type (*const arg)(const size_type) ENTT_NOEXCEPT; + meta_any (*const invoke)(meta_handle, meta_any *const); +}; + +struct meta_template_node { + using size_type = std::size_t; + const size_type arity; + meta_type_node *const type; + meta_type_node *(*const arg)(const size_type)ENTT_NOEXCEPT; +}; + +struct meta_type_node { + using size_type = std::size_t; + const type_info *info; + id_type id; + const meta_traits traits; + meta_type_node *next; + meta_prop_node *prop; + const size_type size_of; + meta_type_node *(*const remove_pointer)() ENTT_NOEXCEPT; + meta_any (*const default_constructor)(); + double (*const conversion_helper)(void *, const void *); + const meta_template_node *const templ; + meta_ctor_node *ctor{nullptr}; + meta_base_node *base{nullptr}; + meta_conv_node *conv{nullptr}; + meta_data_node *data{nullptr}; + meta_func_node *func{nullptr}; + void (*dtor)(void *){nullptr}; +}; + +template +meta_type_node *meta_arg_node(type_list, const std::size_t index) ENTT_NOEXCEPT; + +template +class ENTT_API meta_node { + static_assert(std::is_same_v>>, "Invalid type"); + + [[nodiscard]] static auto *meta_default_constructor() ENTT_NOEXCEPT { + if constexpr(std::is_default_constructible_v) { + return +[]() { return meta_any{std::in_place_type}; }; + } else { + return static_cast>(nullptr); + } + } + + [[nodiscard]] static auto *meta_conversion_helper() ENTT_NOEXCEPT { + if constexpr(std::is_arithmetic_v) { + return +[](void *bin, const void *value) { + return bin ? static_cast(*static_cast(bin) = static_cast(*static_cast(value))) : static_cast(*static_cast(value)); + }; + } else if constexpr(std::is_enum_v) { + return +[](void *bin, const void *value) { + return bin ? static_cast(*static_cast(bin) = static_cast(static_cast>(*static_cast(value)))) : static_cast(*static_cast(value)); + }; + } else { + return static_cast>(nullptr); + } + } + + [[nodiscard]] static meta_template_node *meta_template_info() ENTT_NOEXCEPT { + if constexpr(is_complete_v>) { + static meta_template_node node{ + meta_template_traits::args_type::size, + meta_node::class_type>::resolve(), + [](const std::size_t index) ENTT_NOEXCEPT { return meta_arg_node(typename meta_template_traits::args_type{}, index); } + // tricks clang-format + }; + + return &node; + } else { + return nullptr; + } + } + +public: + [[nodiscard]] static meta_type_node *resolve() ENTT_NOEXCEPT { + static meta_type_node node{ + &type_id(), + {}, + internal::meta_traits::is_none + | (std::is_arithmetic_v ? internal::meta_traits::is_arithmetic : internal::meta_traits::is_none) + | (std::is_array_v ? internal::meta_traits::is_array : internal::meta_traits::is_none) + | (std::is_enum_v ? internal::meta_traits::is_enum : internal::meta_traits::is_none) + | (std::is_class_v ? internal::meta_traits::is_class : internal::meta_traits::is_none) + | (std::is_pointer_v ? internal::meta_traits::is_pointer : internal::meta_traits::is_none) + | (is_meta_pointer_like_v ? internal::meta_traits::is_meta_pointer_like : internal::meta_traits::is_none) + | (is_complete_v> ? internal::meta_traits::is_meta_sequence_container : internal::meta_traits::is_none) + | (is_complete_v> ? internal::meta_traits::is_meta_associative_container : internal::meta_traits::is_none), + nullptr, + nullptr, + size_of_v, + &meta_node>>>::resolve, + meta_default_constructor(), + meta_conversion_helper(), + meta_template_info() + // tricks clang-format + }; + + return &node; + } +}; + +template +[[nodiscard]] meta_type_node *meta_arg_node(type_list, const std::size_t index) ENTT_NOEXCEPT { + meta_type_node *args[sizeof...(Args) + 1u]{nullptr, internal::meta_node>>::resolve()...}; + return args[index + 1u]; +} + +template +[[nodiscard]] static std::decay_t().*Member)> find_by(const Type &info_or_id, const internal::meta_type_node *node) ENTT_NOEXCEPT { + for(auto *curr = node->*Member; curr; curr = curr->next) { + if constexpr(std::is_same_v) { + if(*curr->type->info == info_or_id) { + return curr; + } + } else if constexpr(std::is_same_v) { + if(curr->type->id == info_or_id) { + return curr; + } + } else { + if(curr->id == info_or_id) { + return curr; + } + } + } + + for(auto *curr = node->base; curr; curr = curr->next) { + if(auto *ret = find_by(info_or_id, curr->type); ret) { + return ret; + } + } + + return nullptr; +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/meta/pointer.hpp b/modules/entt/src/entt/meta/pointer.hpp new file mode 100644 index 0000000..58d2f10 --- /dev/null +++ b/modules/entt/src/entt/meta/pointer.hpp @@ -0,0 +1,48 @@ +#ifndef ENTT_META_POINTER_HPP +#define ENTT_META_POINTER_HPP + +#include +#include +#include "type_traits.hpp" + +namespace entt { + +/** + * @brief Makes plain pointers pointer-like types for the meta system. + * @tparam Type Element type. + */ +template +struct is_meta_pointer_like + : std::true_type {}; + +/** + * @brief Partial specialization used to reject pointers to arrays. + * @tparam Type Type of elements of the array. + * @tparam N Number of elements of the array. + */ +template +struct is_meta_pointer_like + : std::false_type {}; + +/** + * @brief Makes `std::shared_ptr`s of any type pointer-like types for the meta + * system. + * @tparam Type Element type. + */ +template +struct is_meta_pointer_like> + : std::true_type {}; + +/** + * @brief Makes `std::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> + : std::true_type {}; + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/meta/policy.hpp b/modules/entt/src/entt/meta/policy.hpp new file mode 100644 index 0000000..91bab43 --- /dev/null +++ b/modules/entt/src/entt/meta/policy.hpp @@ -0,0 +1,66 @@ +#ifndef ENTT_META_POLICY_HPP +#define ENTT_META_POLICY_HPP + +#include + +namespace entt { + +/*! @brief Empty class type used to request the _as ref_ policy. */ +struct as_ref_t { + /** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + template + static constexpr bool value = std::is_reference_v && !std::is_const_v>; + /** + * Internal details not to be documented. + * @endcond + */ +}; + +/*! @brief Empty class type used to request the _as cref_ policy. */ +struct as_cref_t { + /** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + template + static constexpr bool value = std::is_reference_v; + /** + * Internal details not to be documented. + * @endcond + */ +}; + +/*! @brief Empty class type used to request the _as-is_ policy. */ +struct as_is_t { + /** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + template + static constexpr bool value = true; + /** + * Internal details not to be documented. + * @endcond + */ +}; + +/*! @brief Empty class type used to request the _as void_ policy. */ +struct as_void_t { + /** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + template + static constexpr bool value = true; + /** + * Internal details not to be documented. + * @endcond + */ +}; + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/meta/range.hpp b/modules/entt/src/entt/meta/range.hpp new file mode 100644 index 0000000..96c7adb --- /dev/null +++ b/modules/entt/src/entt/meta/range.hpp @@ -0,0 +1,125 @@ +#ifndef ENTT_META_RANGE_HPP +#define ENTT_META_RANGE_HPP + +#include +#include +#include "../core/iterator.hpp" + +namespace entt { + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +struct meta_range_iterator final { + using difference_type = std::ptrdiff_t; + using value_type = Type; + using pointer = input_iterator_pointer; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + using node_type = Node; + + meta_range_iterator() ENTT_NOEXCEPT + : it{} {} + + meta_range_iterator(node_type *head) ENTT_NOEXCEPT + : it{head} {} + + meta_range_iterator &operator++() ENTT_NOEXCEPT { + return (it = it->next), *this; + } + + meta_range_iterator operator++(int) ENTT_NOEXCEPT { + meta_range_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return it; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + [[nodiscard]] bool operator==(const meta_range_iterator &other) const ENTT_NOEXCEPT { + return it == other.it; + } + + [[nodiscard]] bool operator!=(const meta_range_iterator &other) const ENTT_NOEXCEPT { + return !(*this == other); + } + +private: + node_type *it; +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @brief Iterable range to use to iterate all types of meta objects. + * @tparam Type Type of meta objects returned. + * @tparam Node Type of meta nodes iterated. + */ +template +struct meta_range final { + /*! @brief Node type. */ + using node_type = Node; + /*! @brief Input iterator type. */ + using iterator = internal::meta_range_iterator; + /*! @brief Constant input iterator type. */ + using const_iterator = iterator; + + /*! @brief Default constructor. */ + meta_range() ENTT_NOEXCEPT = default; + + /** + * @brief Constructs a meta range from a given node. + * @param head The underlying node with which to construct the range. + */ + meta_range(node_type *head) ENTT_NOEXCEPT + : node{head} {} + + /** + * @brief Returns an iterator to the beginning. + * @return An iterator to the first meta object of the range. + */ + [[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT { + return iterator{node}; + } + + /*! @copydoc cbegin */ + [[nodiscard]] iterator begin() const ENTT_NOEXCEPT { + return cbegin(); + } + + /** + * @brief Returns an iterator to the end. + * @return An iterator to the element following the last meta object of the + * range. + */ + [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { + return iterator{}; + } + + /*! @copydoc cend */ + [[nodiscard]] iterator end() const ENTT_NOEXCEPT { + return cend(); + } + +private: + node_type *node{nullptr}; +}; + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/meta/resolve.hpp b/modules/entt/src/entt/meta/resolve.hpp new file mode 100644 index 0000000..79adc8f --- /dev/null +++ b/modules/entt/src/entt/meta/resolve.hpp @@ -0,0 +1,63 @@ +#ifndef ENTT_META_RESOLVE_HPP +#define ENTT_META_RESOLVE_HPP + +#include +#include "../core/type_info.hpp" +#include "ctx.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. + * @return The meta type associated with the given type, if any. + */ +template +[[nodiscard]] meta_type resolve() ENTT_NOEXCEPT { + return internal::meta_node>>::resolve(); +} + +/** + * @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() ENTT_NOEXCEPT { + return *internal::meta_context::global(); +} + +/** + * @brief Returns the meta type associated with a given identifier, if any. + * @param id Unique identifier. + * @return The meta type associated with the given identifier, if any. + */ +[[nodiscard]] inline meta_type resolve(const id_type id) ENTT_NOEXCEPT { + for(auto &&curr: resolve()) { + if(curr.id() == id) { + return curr; + } + } + + return {}; +} + +/** + * @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) ENTT_NOEXCEPT { + for(auto &&curr: resolve()) { + if(curr.info() == info) { + return curr; + } + } + + return {}; +} + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/meta/template.hpp b/modules/entt/src/entt/meta/template.hpp new file mode 100644 index 0000000..0009b51 --- /dev/null +++ b/modules/entt/src/entt/meta/template.hpp @@ -0,0 +1,27 @@ +#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/modules/entt/src/entt/meta/type_traits.hpp b/modules/entt/src/entt/meta/type_traits.hpp new file mode 100644 index 0000000..51b69f6 --- /dev/null +++ b/modules/entt/src/entt/meta/type_traits.hpp @@ -0,0 +1,55 @@ +#ifndef ENTT_META_TYPE_TRAITS_HPP +#define ENTT_META_TYPE_TRAITS_HPP + +#include +#include + +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` to true if a given type is a + * pointer-like type from the point of view of the meta system, false otherwise. + * @tparam Type Potentially pointer-like type. + */ +template +struct is_meta_pointer_like: std::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/modules/entt/src/entt/meta/utility.hpp b/modules/entt/src/entt/meta/utility.hpp new file mode 100644 index 0000000..150dfb3 --- /dev/null +++ b/modules/entt/src/entt/meta/utility.hpp @@ -0,0 +1,396 @@ +#ifndef ENTT_META_UTILITY_HPP +#define ENTT_META_UTILITY_HPP + +#include +#include +#include +#include +#include "../config/config.h" +#include "../core/type_traits.hpp" +#include "meta.hpp" +#include "node.hpp" +#include "policy.hpp" + +namespace entt { + +/*! @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 { + /*! @brief Meta function return type. */ + using return_type = Ret; + /*! @brief Meta function arguments. */ + using args_type = std::conditional_t, type_list, type_list>; + + /*! @brief True if the meta function is const, false otherwise. */ + static constexpr auto is_const = true; + /*! @brief True if the meta function is static, false otherwise. */ + static constexpr auto is_static = !std::is_base_of_v; +}; + +/** + * @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 { + /*! @brief Meta function return type. */ + using return_type = Ret; + /*! @brief Meta function arguments. */ + using args_type = std::conditional_t, type_list, type_list>; + + /*! @brief True if the meta function is const, false otherwise. */ + static constexpr auto is_const = false; + /*! @brief True if the meta function is static, false otherwise. */ + static constexpr auto is_static = !std::is_base_of_v; +}; + +/** + * @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 { + /*! @brief Meta data return type. */ + using return_type = Ret &; + /*! @brief Meta data arguments. */ + using args_type = std::conditional_t, type_list<>, type_list>; + + /*! @brief True if the meta data is const, false otherwise. */ + static constexpr auto is_const = false; + /*! @brief True if the meta data is static, false otherwise. */ + static constexpr auto is_static = !std::is_base_of_v; +}; + +/** + * @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 { + /*! @brief Meta function return type. */ + using return_type = Ret; + /*! @brief Meta function arguments. */ + using args_type = std::conditional_t>, Type>, type_list, type_list>; + + /*! @brief True if the meta function is const, false otherwise. */ + static constexpr auto is_const = std::is_base_of_v>, Type> && std::is_const_v>; + /*! @brief True if the meta function is static, false otherwise. */ + static constexpr auto is_static = !std::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 { + /*! @brief Meta function return type. */ + using return_type = Ret; + /*! @brief Meta function arguments. */ + using args_type = type_list<>; + + /*! @brief True if the meta function is const, false otherwise. */ + static constexpr auto is_const = false; + /*! @brief True if the meta function is static, false otherwise. */ + static constexpr auto is_static = true; +}; + +/** + * @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 constexpr meta_function_descriptor get_rid_of_noexcept(Ret (Class::*)(Args...) const); + + template + static constexpr meta_function_descriptor get_rid_of_noexcept(Ret (Class::*)(Args...)); + + template + static constexpr meta_function_descriptor get_rid_of_noexcept(Ret Class::*); + + template + static constexpr meta_function_descriptor get_rid_of_noexcept(Ret (*)(Args...)); + + template + static constexpr 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(std::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 = typename meta_function_helper::type; + +/** + * @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 +meta_any meta_dispatch([[maybe_unused]] Type &&value) { + if constexpr(std::is_same_v) { + return meta_any{std::in_place_type}; + } else if constexpr(std::is_same_v) { + return meta_any{std::in_place_type, std::forward(value)}; + } else if constexpr(std::is_same_v) { + static_assert(std::is_lvalue_reference_v, "Invalid type"); + return meta_any{std::in_place_type &>, std::as_const(value)}; + } else { + static_assert(std::is_same_v, "Policy not supported"); + return meta_any{std::forward(value)}; + } +} + +/** + * @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. + * @return The meta type of the i-th element of the list of arguments. + */ +template +[[nodiscard]] static meta_type meta_arg(const std::size_t index) ENTT_NOEXCEPT { + return internal::meta_arg_node(Type{}, 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 value Parameter 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 value) { + if constexpr(!std::is_same_v && !std::is_same_v) { + if constexpr(std::is_member_function_pointer_v || std::is_function_v>>) { + using descriptor = meta_function_helper_t; + using data_type = type_list_element_t; + + if(auto *const clazz = instance->try_cast(); clazz && value.allow_cast()) { + std::invoke(Data, *clazz, value.cast()); + return true; + } + } else if constexpr(std::is_member_object_pointer_v) { + using data_type = std::remove_reference_t::return_type>; + + if constexpr(!std::is_array_v && !std::is_const_v) { + if(auto *const clazz = instance->try_cast(); clazz && value.allow_cast()) { + std::invoke(Data, *clazz) = value.cast(); + return true; + } + } + } else { + using data_type = std::remove_reference_t; + + if constexpr(!std::is_array_v && !std::is_const_v) { + if(value.allow_cast()) { + *Data = value.cast(); + return true; + } + } + } + } + + return false; +} + +/** + * @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([[maybe_unused]] meta_handle instance) { + if constexpr(std::is_member_pointer_v || std::is_function_v>>) { + if constexpr(!std::is_array_v>>>) { + if constexpr(std::is_invocable_v) { + if(auto *clazz = instance->try_cast(); clazz) { + return meta_dispatch(std::invoke(Data, *clazz)); + } + } + + if constexpr(std::is_invocable_v) { + if(auto *fallback = instance->try_cast(); fallback) { + return meta_dispatch(std::invoke(Data, *fallback)); + } + } + } + + return meta_any{}; + } else if constexpr(std::is_pointer_v) { + if constexpr(std::is_array_v>) { + return meta_any{}; + } else { + return meta_dispatch(*Data); + } + } else { + return meta_dispatch(Data); + } +} + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +[[nodiscard]] meta_any meta_invoke_with_args(Candidate &&candidate, Args &&...args) { + if constexpr(std::is_same_v, void>) { + std::invoke(candidate, args...); + return meta_any{std::in_place_type}; + } else { + return meta_dispatch(std::invoke(candidate, args...)); + } +} + +template +[[nodiscard]] meta_any meta_invoke([[maybe_unused]] meta_handle instance, Candidate &&candidate, [[maybe_unused]] meta_any *args, std::index_sequence) { + using descriptor = meta_function_helper_t>; + + if constexpr(std::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(std::forward(candidate), *clazz, (args + Index)->cast>()...); + } + } else if constexpr(std::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(std::forward(candidate), *clazz, (args + Index)->cast>()...); + } + } else { + if(((args + Index)->allow_cast>() && ...)) { + return meta_invoke_with_args(std::forward(candidate), (args + Index)->cast>()...); + } + } + + return meta_any{}; +} + +template +[[nodiscard]] meta_any meta_construct(meta_any *const args, std::index_sequence) { + if(((args + Index)->allow_cast() && ...)) { + return meta_any{std::in_place_type, (args + Index)->cast()...}; + } + + return meta_any{}; +} + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ + +/** + * @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([[maybe_unused]] meta_handle instance, Candidate &&candidate, [[maybe_unused]] meta_any *const args) { + return internal::meta_invoke(std::move(instance), std::forward(candidate), args, std::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(std::move(instance), Candidate, args, std::make_index_sequence>::args_type::size>{}); +} + +/** + * @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 internal::meta_construct(args, std::index_sequence_for{}); +} + +/** + * @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 args Parameters to use to _invoke_ the object. + * @param candidate The actual object to _invoke_. + * @return A meta any containing the returned value, if any. + */ +template +[[nodiscard]] meta_any meta_construct(Candidate &&candidate, meta_any *const args) { + if constexpr(meta_function_helper_t::is_static) { + return internal::meta_invoke({}, std::forward(candidate), args, std::make_index_sequence>::args_type::size>{}); + } else { + return internal::meta_invoke(*args, std::forward(candidate), args + 1u, std::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 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(Candidate, args); +} + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/platform/android-ndk-r17.hpp b/modules/entt/src/entt/platform/android-ndk-r17.hpp new file mode 100644 index 0000000..5696123 --- /dev/null +++ b/modules/entt/src/entt/platform/android-ndk-r17.hpp @@ -0,0 +1,67 @@ +#ifndef ENTT_PLATFORM_ANDROID_NDK_R17_HPP +#define ENTT_PLATFORM_ANDROID_NDK_R17_HPP + +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +#ifdef __ANDROID__ +# include +# if __NDK_MAJOR__ == 17 + +# include +# include +# include + +namespace std { + +namespace internal { + +template +constexpr auto is_invocable(int) -> decltype(std::invoke(std::declval(), std::declval()...), std::true_type{}); + +template +constexpr std::false_type is_invocable(...); + +template +constexpr auto is_invocable_r(int) +-> std::enable_if_t(), std::declval()...)), Ret>, std::true_type>; + + +template +constexpr std::false_type is_invocable_r(...); + +} // namespace internal + +template +struct is_invocable: decltype(internal::is_invocable(0)) {}; + +template +inline constexpr bool is_invocable_v = std::is_invocable::value; + +template +struct is_invocable_r: decltype(internal::is_invocable_r(0)) {}; + +template +inline constexpr bool is_invocable_r_v = std::is_invocable_r::value; + +template +struct invoke_result { + using type = decltype(std::invoke(std::declval(), std::declval()...)); +}; + +template +using invoke_result_t = typename std::invoke_result::type; + +} // namespace std + +# endif +#endif + +/** + * Internal details not to be documented. + * @endcond + */ + +#endif diff --git a/modules/entt/src/entt/poly/fwd.hpp b/modules/entt/src/entt/poly/fwd.hpp new file mode 100644 index 0000000..1435337 --- /dev/null +++ b/modules/entt/src/entt/poly/fwd.hpp @@ -0,0 +1,21 @@ +#ifndef ENTT_POLY_FWD_HPP +#define ENTT_POLY_FWD_HPP + +#include +#include + +namespace entt { + +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/modules/entt/src/entt/poly/poly.hpp b/modules/entt/src/entt/poly/poly.hpp new file mode 100644 index 0000000..631038d --- /dev/null +++ b/modules/entt/src/entt/poly/poly.hpp @@ -0,0 +1,314 @@ +#ifndef ENTT_POLY_POLY_HPP +#define ENTT_POLY_POLY_HPP + +#include +#include +#include +#include +#include +#include "../config/config.h" +#include "../core/any.hpp" +#include "../core/type_info.hpp" +#include "../core/type_traits.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 + poly_inspector invoke(Args &&...args) const; + + /*! @copydoc invoke */ + template + 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 = typename Concept::template type; + + template + static auto vtable_entry(Ret (*)(inspector &, Args...)) -> Ret (*)(basic_any &, Args...); + + template + static auto vtable_entry(Ret (*)(const inspector &, Args...)) -> Ret (*)(const basic_any &, Args...); + + template + static auto vtable_entry(Ret (*)(Args...)) -> Ret (*)(const basic_any &, Args...); + + template + static auto vtable_entry(Ret (inspector::*)(Args...)) -> Ret (*)(basic_any &, Args...); + + template + static auto vtable_entry(Ret (inspector::*)(Args...) const) -> Ret (*)(const basic_any &, Args...); + + template + static auto make_vtable(value_list) ENTT_NOEXCEPT + -> decltype(std::make_tuple(vtable_entry(Candidate)...)); + + template + [[nodiscard]] static constexpr auto make_vtable(type_list) ENTT_NOEXCEPT { + if constexpr(sizeof...(Func) == 0u) { + return decltype(make_vtable(typename Concept::template impl{})){}; + } else if constexpr((std::is_function_v && ...)) { + return decltype(std::make_tuple(vtable_entry(std::declval())...)){}; + } + } + + template + static void fill_vtable_entry(Ret (*&entry)(Any &, Args...)) ENTT_NOEXCEPT { + if constexpr(std::is_invocable_r_v) { + entry = +[](Any &, Args... args) -> Ret { + return std::invoke(Candidate, std::forward(args)...); + }; + } else { + entry = +[](Any &instance, Args... args) -> Ret { + return static_cast(std::invoke(Candidate, any_cast &>(instance), std::forward(args)...)); + }; + } + } + + template + [[nodiscard]] static auto fill_vtable(std::index_sequence) ENTT_NOEXCEPT { + vtable_type impl{}; + (fill_vtable_entry>>(std::get(impl)), ...); + return impl; + } + + using vtable_type = decltype(make_vtable(Concept{})); + static constexpr bool is_mono_v = std::tuple_size_v == 1u; + +public: + /*! @brief Virtual table type. */ + using type = std::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() ENTT_NOEXCEPT { + static_assert(std::is_same_v>, "Type differs from its decayed form"); + static const vtable_type vtable = fill_vtable(std::make_index_sequence::size>{}); + + if constexpr(is_mono_v) { + return std::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(std::is_function_v>) { + return poly.vtable(poly.storage, std::forward(args)...); + } else { + return std::get(*poly.vtable)(poly.storage, std::forward(args)...); + } + } + + /*! @copydoc invoke */ + template + [[nodiscard]] decltype(auto) invoke(poly_base &self, Args &&...args) { + auto &poly = static_cast(self); + + if constexpr(std::is_function_v>) { + static_assert(Member == 0u, "Unknown member"); + return poly.vtable(poly.storage, std::forward(args)...); + } else { + return std::get(*poly.vtable)(poly.storage, std::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 std::forward(self).template invoke(self, std::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>> { + /*! @brief A poly base is allowed to snoop into a poly object. */ + friend struct poly_base; + +public: + /*! @brief Concept type. */ + using concept_type = typename Concept::template type>; + /*! @brief Virtual table type. */ + using vtable_type = typename poly_vtable::type; + + /*! @brief Default constructor. */ + basic_poly() ENTT_NOEXCEPT + : storage{}, + vtable{} {} + + /** + * @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(std::in_place_type_t, Args &&...args) + : storage{std::in_place_type, std::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>, basic_poly>>> + basic_poly(Type &&value) ENTT_NOEXCEPT + : basic_poly{std::in_place_type>>, std::forward(value)} {} + + /** + * @brief Returns the object type if any, `type_id()` otherwise. + * @return The object type if any, `type_id()` otherwise. + */ + [[nodiscard]] const type_info &type() const ENTT_NOEXCEPT { + return storage.type(); + } + + /** + * @brief Returns an opaque pointer to the contained instance. + * @return An opaque pointer the contained instance, if any. + */ + [[nodiscard]] const void *data() const ENTT_NOEXCEPT { + return storage.data(); + } + + /*! @copydoc data */ + [[nodiscard]] void *data() ENTT_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(std::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 ENTT_NOEXCEPT { + return static_cast(storage); + } + + /** + * @brief Returns a pointer to the underlying concept. + * @return A pointer to the underlying concept. + */ + [[nodiscard]] concept_type *operator->() ENTT_NOEXCEPT { + return this; + } + + /*! @copydoc operator-> */ + [[nodiscard]] const concept_type *operator->() const ENTT_NOEXCEPT { + return this; + } + + /** + * @brief Aliasing constructor. + * @return A poly that shares a reference to an unmanaged object. + */ + [[nodiscard]] basic_poly as_ref() ENTT_NOEXCEPT { + basic_poly ref{}; + ref.storage = storage.as_ref(); + ref.vtable = vtable; + return ref; + } + + /*! @copydoc as_ref */ + [[nodiscard]] basic_poly as_ref() const ENTT_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/modules/entt/src/entt/process/process.hpp b/modules/entt/src/entt/process/process.hpp index 931bafd..22e2b37 100644 --- a/modules/entt/src/entt/process/process.hpp +++ b/modules/entt/src/entt/process/process.hpp @@ -1,15 +1,13 @@ #ifndef ENTT_PROCESS_PROCESS_HPP #define ENTT_PROCESS_PROCESS_HPP - -#include +#include #include +#include #include "../config/config.h" - namespace entt { - /** * @brief Base class for processes. * @@ -71,51 +69,48 @@ namespace entt { */ template class process { - enum class state: unsigned int { - UNINITIALIZED = 0, - RUNNING, - PAUSED, - SUCCEEDED, - FAILED, - ABORTED, - FINISHED + enum class state : std::uint8_t { + uninitialized = 0, + running, + paused, + succeeded, + failed, + aborted, + finished, + rejected }; - template - using state_value_t = std::integral_constant; - template - auto tick(int, state_value_t) - -> decltype(std::declval().init()) { + auto next(std::integral_constant) + -> decltype(std::declval().init(), void()) { static_cast(this)->init(); } template - auto tick(int, state_value_t, Delta delta, void *data) - -> decltype(std::declval().update(delta, data)) { + auto next(std::integral_constant, Delta delta, void *data) + -> decltype(std::declval().update(delta, data), void()) { static_cast(this)->update(delta, data); } template - auto tick(int, state_value_t) - -> decltype(std::declval().succeeded()) { + auto next(std::integral_constant) + -> decltype(std::declval().succeeded(), void()) { static_cast(this)->succeeded(); } template - auto tick(int, state_value_t) - -> decltype(std::declval().failed()) { + auto next(std::integral_constant) + -> decltype(std::declval().failed(), void()) { static_cast(this)->failed(); } template - auto tick(int, state_value_t) - -> decltype(std::declval().aborted()) { + auto next(std::integral_constant) + -> decltype(std::declval().aborted(), void()) { static_cast(this)->aborted(); } - template - void tick(char, state_value_t, Args &&...) const ENTT_NOEXCEPT {} + void next(...) const ENTT_NOEXCEPT {} protected: /** @@ -126,7 +121,7 @@ protected: */ void succeed() ENTT_NOEXCEPT { if(alive()) { - current = state::SUCCEEDED; + current = state::succeeded; } } @@ -138,7 +133,7 @@ protected: */ void fail() ENTT_NOEXCEPT { if(alive()) { - current = state::FAILED; + current = state::failed; } } @@ -149,8 +144,8 @@ protected: * running. */ void pause() ENTT_NOEXCEPT { - if(current == state::RUNNING) { - current = state::PAUSED; + if(current == state::running) { + current = state::paused; } } @@ -161,8 +156,8 @@ protected: * paused. */ void unpause() ENTT_NOEXCEPT { - if(current == state::PAUSED) { - current = state::RUNNING; + if(current == state::paused) { + current = state::running; } } @@ -172,7 +167,7 @@ public: /*! @brief Default destructor. */ virtual ~process() ENTT_NOEXCEPT { - static_assert(std::is_base_of_v); + static_assert(std::is_base_of_v, "Incorrect use of the class template"); } /** @@ -183,12 +178,12 @@ public: * * @param immediately Requests an immediate operation. */ - void abort(const bool immediately = false) ENTT_NOEXCEPT { + void abort(const bool immediately = false) { if(alive()) { - current = state::ABORTED; + current = state::aborted; if(immediately) { - tick(0); + tick({}); } } } @@ -197,32 +192,32 @@ public: * @brief Returns true if a process is either running or paused. * @return True if the process is still alive, false otherwise. */ - bool alive() const ENTT_NOEXCEPT { - return current == state::RUNNING || current == state::PAUSED; + [[nodiscard]] bool alive() const ENTT_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. */ - bool dead() const ENTT_NOEXCEPT { - return current == state::FINISHED; + [[nodiscard]] bool finished() const ENTT_NOEXCEPT { + return current == state::finished; } /** * @brief Returns true if a process is currently paused. * @return True if the process is paused, false otherwise. */ - bool paused() const ENTT_NOEXCEPT { - return current == state::PAUSED; + [[nodiscard]] bool paused() const ENTT_NOEXCEPT { + return current == state::paused; } /** * @brief Returns true if a process terminated with errors. * @return True if the process terminated with errors, false otherwise. */ - bool rejected() const ENTT_NOEXCEPT { - return stopped; + [[nodiscard]] bool rejected() const ENTT_NOEXCEPT { + return current == state::rejected; } /** @@ -231,13 +226,13 @@ public: * @param data Optional data. */ void tick(const Delta delta, void *data = nullptr) { - switch (current) { - case state::UNINITIALIZED: - tick(0, state_value_t{}); - current = state::RUNNING; + switch(current) { + case state::uninitialized: + next(std::integral_constant{}); + current = state::running; break; - case state::RUNNING: - tick(0, state_value_t{}, delta, data); + case state::running: + next(std::integral_constant{}, delta, data); break; default: // suppress warnings @@ -246,19 +241,17 @@ public: // if it's dead, it must be notified and removed immediately switch(current) { - case state::SUCCEEDED: - tick(0, state_value_t{}); - current = state::FINISHED; + case state::succeeded: + next(std::integral_constant{}); + current = state::finished; break; - case state::FAILED: - tick(0, state_value_t{}); - current = state::FINISHED; - stopped = true; + case state::failed: + next(std::integral_constant{}); + current = state::rejected; break; - case state::ABORTED: - tick(0, state_value_t{}); - current = state::FINISHED; - stopped = true; + case state::aborted: + next(std::integral_constant{}); + current = state::rejected; break; default: // suppress warnings @@ -267,11 +260,9 @@ public: } private: - state current{state::UNINITIALIZED}; - bool stopped{false}; + state current{state::uninitialized}; }; - /** * @brief Adaptor for lambdas and functors to turn them into processes. * @@ -319,9 +310,8 @@ struct process_adaptor: process, Delta>, private Fu * @param args Parameters to use to initialize the actual process. */ template - process_adaptor(Args &&... args) - : Func{std::forward(args)...} - {} + process_adaptor(Args &&...args) + : Func{std::forward(args)...} {} /** * @brief Updates a process and its internal state if required. @@ -329,12 +319,14 @@ struct process_adaptor: process, Delta>, private Fu * @param data Optional data. */ void update(const Delta delta, void *data) { - Func::operator()(delta, data, [this]() { this->succeed(); }, [this]() { this->fail(); }); + Func::operator()( + delta, + data, + [this]() { this->succeed(); }, + [this]() { this->fail(); }); } }; +} // namespace entt -} - - -#endif // ENTT_PROCESS_PROCESS_HPP +#endif diff --git a/modules/entt/src/entt/process/scheduler.hpp b/modules/entt/src/entt/process/scheduler.hpp index 6449ad0..82b215c 100644 --- a/modules/entt/src/entt/process/scheduler.hpp +++ b/modules/entt/src/entt/process/scheduler.hpp @@ -1,19 +1,17 @@ #ifndef ENTT_PROCESS_SCHEDULER_HPP #define ENTT_PROCESS_SCHEDULER_HPP - -#include -#include -#include #include +#include +#include #include +#include +#include #include "../config/config.h" #include "process.hpp" - namespace entt { - /** * @brief Cooperative scheduler for processes. * @@ -43,9 +41,9 @@ namespace entt { template class scheduler { struct process_handler { - using instance_type = std::unique_ptr; - using update_fn_type = bool(process_handler &, Delta, void *); - using abort_fn_type = void(process_handler &, bool); + using instance_type = std::unique_ptr; + using update_fn_type = bool(scheduler &, std::size_t, Delta, void *); + using abort_fn_type = void(scheduler &, std::size_t, bool); using next_type = std::unique_ptr; instance_type instance; @@ -55,15 +53,12 @@ class scheduler { }; struct continuation { - continuation(process_handler *ref) - : handler{ref} - { - ENTT_ASSERT(handler); - } + continuation(process_handler *ref) ENTT_NOEXCEPT + : handler{ref} {} template - continuation then(Args &&... args) { - static_assert(std::is_base_of_v, Proc>); + continuation then(Args &&...args) { + static_assert(std::is_base_of_v, Proc>, "Invalid process type"); auto proc = typename process_handler::instance_type{new Proc{std::forward(args)...}, &scheduler::deleter}; handler->next.reset(new process_handler{std::move(proc), &scheduler::update, &scheduler::abort, nullptr}); handler = handler->next.get(); @@ -80,28 +75,28 @@ class scheduler { }; template - static bool update(process_handler &handler, const Delta delta, void *data) { - auto *process = static_cast(handler.instance.get()); + [[nodiscard]] static bool update(scheduler &owner, std::size_t pos, const Delta delta, void *data) { + auto *process = static_cast(owner.handlers[pos].instance.get()); process->tick(delta, data); - auto dead = process->dead(); - - if(dead) { - if(handler.next && !process->rejected()) { + if(process->rejected()) { + return true; + } else if(process->finished()) { + if(auto &&handler = owner.handlers[pos]; handler.next) { handler = std::move(*handler.next); // forces the process to exit the uninitialized state - dead = handler.update(handler, {}, nullptr); - } else { - handler.instance.reset(); + return handler.update(owner, pos, {}, nullptr); } + + return true; } - return dead; + return false; } template - static void abort(process_handler &handler, const bool immediately) { - static_cast(handler.instance.get())->abort(immediately); + static void abort(scheduler &owner, std::size_t pos, const bool immediately) { + static_cast(owner.handlers[pos].instance.get())->abort(immediately); } template @@ -111,22 +106,22 @@ class scheduler { public: /*! @brief Unsigned integer type. */ - using size_type = typename std::vector::size_type; + using size_type = std::size_t; /*! @brief Default constructor. */ - scheduler() ENTT_NOEXCEPT = default; + scheduler() = default; /*! @brief Default move constructor. */ scheduler(scheduler &&) = default; /*! @brief Default move assignment operator. @return This scheduler. */ - scheduler & operator=(scheduler &&) = default; + scheduler &operator=(scheduler &&) = default; /** * @brief Number of processes currently scheduled. * @return Number of processes currently scheduled. */ - size_type size() const ENTT_NOEXCEPT { + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { return handlers.size(); } @@ -134,7 +129,7 @@ public: * @brief Returns true if at least a process is currently scheduled. * @return True if there are scheduled processes, false otherwise. */ - bool empty() const ENTT_NOEXCEPT { + [[nodiscard]] bool empty() const ENTT_NOEXCEPT { return handlers.empty(); } @@ -174,13 +169,13 @@ public: * @return An opaque object to use to concatenate processes. */ template - auto attach(Args &&... args) { - static_assert(std::is_base_of_v, Proc>); + auto attach(Args &&...args) { + static_assert(std::is_base_of_v, Proc>, "Invalid process type"); auto proc = typename process_handler::instance_type{new Proc{std::forward(args)...}, &scheduler::deleter}; - process_handler handler{std::move(proc), &scheduler::update, &scheduler::abort, nullptr}; + auto &&ref = handlers.emplace_back(process_handler{std::move(proc), &scheduler::update, &scheduler::abort, nullptr}); // forces the process to exit the uninitialized state - handler.update(handler, {}, nullptr); - return continuation{&handlers.emplace_back(std::move(handler))}; + ref.update(*this, handlers.size() - 1u, {}, nullptr); + return continuation{&handlers.back()}; } /** @@ -252,18 +247,13 @@ public: * @param data Optional data. */ void update(const Delta delta, void *data = nullptr) { - bool clean = false; - for(auto pos = handlers.size(); pos; --pos) { - auto &handler = handlers[pos-1]; - const bool dead = handler.update(handler, delta, data); - clean = clean || dead; - } + const auto curr = pos - 1u; - if(clean) { - handlers.erase(std::remove_if(handlers.begin(), handlers.end(), [](auto &handler) { - return !handler.instance; - }), handlers.end()); + if(const auto dead = handlers[curr].update(*this, curr, delta, data); dead) { + std::swap(handlers[curr], handlers.back()); + handlers.pop_back(); + } } } @@ -278,23 +268,16 @@ public: * @param immediately Requests an immediate operation. */ void abort(const bool immediately = false) { - decltype(handlers) exec; - exec.swap(handlers); - - std::for_each(exec.begin(), exec.end(), [immediately](auto &handler) { - handler.abort(handler, immediately); - }); - - std::move(handlers.begin(), handlers.end(), std::back_inserter(exec)); - handlers.swap(exec); + for(auto pos = handlers.size(); pos; --pos) { + const auto curr = pos - 1u; + handlers[curr].abort(*this, curr, immediately); + } } private: std::vector handlers{}; }; +} // namespace entt -} - - -#endif // ENTT_PROCESS_SCHEDULER_HPP +#endif diff --git a/modules/entt/src/entt/resource/cache.hpp b/modules/entt/src/entt/resource/cache.hpp index b582e4b..00b3b78 100644 --- a/modules/entt/src/entt/resource/cache.hpp +++ b/modules/entt/src/entt/resource/cache.hpp @@ -1,207 +1,424 @@ -#ifndef ENTT_RESOURCE_CACHE_HPP -#define ENTT_RESOURCE_CACHE_HPP - +#ifndef ENTT_RESOURCE_RESOURCE_CACHE_HPP +#define ENTT_RESOURCE_RESOURCE_CACHE_HPP +#include +#include +#include #include -#include +#include #include -#include +#include #include "../config/config.h" -#include "../core/hashed_string.hpp" -#include "handle.hpp" -#include "loader.hpp" +#include "../container/dense_map.hpp" +#include "../core/compressed_pair.hpp" +#include "../core/fwd.hpp" +#include "../core/iterator.hpp" +#include "../core/utility.hpp" #include "fwd.hpp" - +#include "loader.hpp" +#include "resource.hpp" namespace entt { +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +template +class resource_cache_iterator final { + template + friend class resource_cache_iterator; + +public: + using value_type = std::pair>; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + resource_cache_iterator() ENTT_NOEXCEPT = default; + + resource_cache_iterator(const It iter) ENTT_NOEXCEPT + : it{iter} {} + + template && std::is_constructible_v>> + resource_cache_iterator(const resource_cache_iterator, Other> &other) ENTT_NOEXCEPT + : it{other.it} {} + + resource_cache_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } + + resource_cache_iterator operator++(int) ENTT_NOEXCEPT { + resource_cache_iterator orig = *this; + return ++(*this), orig; + } + + resource_cache_iterator &operator--() ENTT_NOEXCEPT { + return --it, *this; + } + + resource_cache_iterator operator--(int) ENTT_NOEXCEPT { + resource_cache_iterator orig = *this; + return operator--(), orig; + } + + resource_cache_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + it += value; + return *this; + } + + resource_cache_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + resource_cache_iterator copy = *this; + return (copy += value); + } + + resource_cache_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); + } + + resource_cache_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); + } + + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + return {it[value].first, resource{it[value].second}}; + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return (*this)[0]; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + template + friend std::ptrdiff_t operator-(const resource_cache_iterator &, const resource_cache_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator==(const resource_cache_iterator &, const resource_cache_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator<(const resource_cache_iterator &, const resource_cache_iterator &) ENTT_NOEXCEPT; + +private: + It it; +}; + +template +[[nodiscard]] std::ptrdiff_t operator-(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it - rhs.it; +} + +template +[[nodiscard]] bool operator==(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] bool operator!=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +[[nodiscard]] bool operator<(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it < rhs.it; +} + +template +[[nodiscard]] bool operator>(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +template +[[nodiscard]] bool operator<=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +template +[[nodiscard]] bool operator>=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +} // namespace internal /** - * @brief Simple cache for resources of a given type. - * - * Minimal implementation of a cache for resources of a given type. It doesn't - * offer much functionalities but it's suitable for small or medium sized - * applications and can be freely inherited to add targeted functionalities for - * large sized applications. - * - * @tparam Resource Type of resources managed by a cache. + * Internal details not to be documented. + * @endcond */ -template + +/** + * @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 container_type = std::unordered_map>; + using alloc_traits = typename std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using container_allocator = typename alloc_traits::template rebind_alloc>; + using container_type = dense_map, container_allocator>; public: + /*! @brief Resource type. */ + using value_type = Type; /*! @brief Unsigned integer type. */ - using size_type = typename container_type::size_type; - /*! @brief Type of resources managed by a cache. */ - using resource_type = typename hashed_string::hash_type; + using size_type = std::size_t; + /*! @brief Loader type. */ + using loader_type = Loader; + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @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() = default; + 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{std::piecewise_construct, std::forward_as_tuple(other.pool.first(), allocator), std::forward_as_tuple(other.pool.second())} {} /*! @brief Default move constructor. */ resource_cache(resource_cache &&) = default; - /*! @brief Default move assignment operator. @return This cache. */ - resource_cache & operator=(resource_cache &&) = 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{std::piecewise_construct, std::forward_as_tuple(std::move(other.pool.first()), allocator), std::forward_as_tuple(std::move(other.pool.second()))} {} + + /** + * @brief Default copy assignment operator. + * @return This cache. + */ + resource_cache &operator=(const resource_cache &) = default; /** - * @brief Number of resources managed by a cache. - * @return Number of resources currently stored. + * @brief Default move assignment operator. + * @return This cache. */ - size_type size() const ENTT_NOEXCEPT { - return resources.size(); + resource_cache &operator=(resource_cache &&) = default; + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return pool.first().get_allocator(); + } + + /** + * @brief Returns an iterator to the beginning. + * + * The returned iterator points to the first instance of the cache. 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 ENTT_NOEXCEPT { + return pool.first().begin(); + } + + /*! @copydoc cbegin */ + [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT { + return cbegin(); + } + + /*! @copydoc begin */ + [[nodiscard]] iterator begin() ENTT_NOEXCEPT { + return pool.first().begin(); + } + + /** + * @brief Returns an iterator to the end. + * + * The returned iterator points to the element following the last instance + * of the cache. Attempting to dereference the returned iterator results in + * undefined behavior. + * + * @return An iterator to the element following the last instance of the + * internal cache. + */ + [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT { + return pool.first().end(); + } + + /*! @copydoc cend */ + [[nodiscard]] const_iterator end() const ENTT_NOEXCEPT { + return cend(); + } + + /*! @copydoc end */ + [[nodiscard]] iterator end() ENTT_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. */ - bool empty() const ENTT_NOEXCEPT { - return resources.empty(); + [[nodiscard]] bool empty() const ENTT_NOEXCEPT { + return pool.first().empty(); } /** - * @brief Clears a cache and discards all its resources. - * - * Handles are not invalidated and the memory used by a resource isn't - * freed as long as at least a handle keeps the resource itself alive. + * @brief Number of resources managed by a cache. + * @return Number of resources currently stored. */ + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return pool.first().size(); + } + + /*! @brief Clears a cache. */ void clear() ENTT_NOEXCEPT { - resources.clear(); + pool.first().clear(); } /** - * @brief Loads the resource that corresponds to a given identifier. + * @brief Loads a resource, if its identifier does not exist. * - * In case an identifier isn't already present in the cache, it loads its - * resource and stores it aside for future uses. Arguments are forwarded - * directly to the loader in order to construct properly the requested - * resource. - * - * @note - * If the identifier is already present in the cache, this function does - * nothing and the arguments are simply discarded. + * Arguments are forwarded directly to the loader and _consumed_ only if the + * resource doesn't already exist. * * @warning - * If the resource cannot be loaded correctly, the returned handle will be + * If the resource isn't loaded correctly, the returned handle could be * invalid and any use of it will result in undefined behavior. * - * @tparam Loader Type of loader to use to load the resource if required. * @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 handle for the given resource. + * @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 - resource_handle load(const resource_type id, Args &&... args) { - static_assert(std::is_base_of_v, Loader>); - resource_handle handle{}; - - if(auto it = resources.find(id); it == resources.cend()) { - if(auto resource = Loader{}.get(std::forward(args)...); resource) { - resources[id] = resource; - handle = std::move(resource); - } - } else { - handle = it->second; + template + std::pair load(const id_type id, Args &&...args) { + if(auto it = pool.first().find(id); it != pool.first().end()) { + return {it, false}; } - return handle; + return pool.first().emplace(id, pool.second()(std::forward(args)...)); } /** - * @brief Reloads a resource or loads it for the first time if not present. - * - * Equivalent to the following snippet (pseudocode): - * - * @code{.cpp} - * cache.discard(id); - * cache.load(id, args...); - * @endcode - * - * Arguments are forwarded directly to the loader in order to construct - * properly the requested resource. + * @brief Force loads a resource, if its identifier does not exist. + * @copydetails load + */ + template + std::pair force_load(const id_type id, Args &&...args) { + return {pool.first().insert_or_assign(id, pool.second()(std::forward(args)...)).first, true}; + } + + /** + * @brief Returns a handle for a given resource identifier. * * @warning - * If the resource cannot be loaded correctly, the returned handle will be - * invalid and any use of it will result in undefined behavior. + * There is no guarantee that the returned handle is valid.
+ * If it is not, any use will result in indefinite behavior. * - * @tparam Loader Type of loader to use to load the resource. - * @tparam Args Types of arguments to use to load the resource. * @param id Unique resource identifier. - * @param args Arguments to use to load the resource. * @return A handle for the given resource. */ - template - resource_handle reload(const resource_type id, Args &&... args) { - return (discard(id), load(id, std::forward(args)...)); + [[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 Creates a temporary handle for a resource. - * - * Arguments are forwarded directly to the loader in order to construct - * properly the requested resource. The handle isn't stored aside and the - * cache isn't in charge of the lifetime of the resource itself. - * - * @tparam Loader Type of loader to use to load the resource. - * @tparam Args Types of arguments to use to load the resource. - * @param args Arguments to use to load the resource. - * @return A handle for the given resource. + * @brief Checks if a cache contains a given identifier. + * @param id Unique resource identifier. + * @return True if the cache contains the resource, false otherwise. */ - template - resource_handle temp(Args &&... args) const { - return { Loader{}.get(std::forward(args)...) }; + [[nodiscard]] bool contains(const id_type id) const { + return pool.first().contains(id); } /** - * @brief Creates a handle for a given resource identifier. - * - * A resource handle can be in a either valid or invalid state. In other - * terms, a resource handle is properly initialized with a resource if the - * cache contains the resource itself. Otherwise the returned handle is - * uninitialized and accessing it results in undefined behavior. - * - * @sa resource_handle - * - * @param id Unique resource identifier. - * @return A handle for the given resource. + * @brief Removes an element from a given position. + * @param pos An iterator to the element to remove. + * @return An iterator following the removed element. */ - resource_handle handle(const resource_type id) const { - auto it = resources.find(id); - return { it == resources.end() ? nullptr : it->second }; + iterator erase(const_iterator pos) { + const auto it = pool.first().begin(); + return pool.first().erase(it + (pos - const_iterator{it})); } /** - * @brief Checks if a cache contains a given identifier. - * @param id Unique resource identifier. - * @return True if the cache contains the resource, false otherwise. + * @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. */ - bool contains(const resource_type id) const ENTT_NOEXCEPT { - return (resources.find(id) != resources.cend()); + 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 Discards the resource that corresponds to a given identifier. - * - * Handles are not invalidated and the memory used by the resource isn't - * freed as long as at least a handle keeps the resource itself alive. - * + * @brief Removes the given elements from a cache. * @param id Unique resource identifier. + * @return Number of resources erased (either 0 or 1). */ - void discard(const resource_type id) ENTT_NOEXCEPT { - if(auto it = resources.find(id); it != resources.end()) { - resources.erase(it); - } + 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: - container_type resources; + compressed_pair pool; }; +} // namespace entt -} - - -#endif // ENTT_RESOURCE_CACHE_HPP +#endif diff --git a/modules/entt/src/entt/resource/fwd.hpp b/modules/entt/src/entt/resource/fwd.hpp index e85fe6b..ed63da5 100644 --- a/modules/entt/src/entt/resource/fwd.hpp +++ b/modules/entt/src/entt/resource/fwd.hpp @@ -1,27 +1,19 @@ #ifndef ENTT_RESOURCE_FWD_HPP #define ENTT_RESOURCE_FWD_HPP - -#include "../config/config.h" - +#include namespace entt { - -/*! @class resource_cache */ template +struct resource_loader; + +template, typename = std::allocator> class resource_cache; -/*! @class resource_handle */ template -class resource_handle; - -/*! @class resource_loader */ -template -class resource_loader; - - -} +class resource; +} // namespace entt -#endif // ENTT_RESOURCE_FWD_HPP +#endif diff --git a/modules/entt/src/entt/resource/handle.hpp b/modules/entt/src/entt/resource/handle.hpp deleted file mode 100644 index a19d9f6..0000000 --- a/modules/entt/src/entt/resource/handle.hpp +++ /dev/null @@ -1,106 +0,0 @@ -#ifndef ENTT_RESOURCE_HANDLE_HPP -#define ENTT_RESOURCE_HANDLE_HPP - - -#include -#include -#include "../config/config.h" -#include "fwd.hpp" - - -namespace entt { - - -/** - * @brief Shared resource handle. - * - * A shared resource handle is a small class that wraps a resource and keeps it - * alive even if it's deleted from the cache. It can be either copied or - * moved. A handle shares a reference to the same resource with all the other - * handles constructed for the same identifier.
- * As a rule of thumb, resources should never be copied nor moved. Handles are - * the way to go to keep references to them. - * - * @tparam Resource Type of resource managed by a handle. - */ -template -class resource_handle { - /*! @brief Resource handles are friends of their caches. */ - friend class resource_cache; - - resource_handle(std::shared_ptr res) ENTT_NOEXCEPT - : resource{std::move(res)} - {} - -public: - /*! @brief Default constructor. */ - resource_handle() ENTT_NOEXCEPT = default; - - /** - * @brief Gets a reference to the managed resource. - * - * @warning - * The behavior is undefined if the handle doesn't contain a resource.
- * An assertion will abort the execution at runtime in debug mode if the - * handle is empty. - * - * @return A reference to the managed resource. - */ - const Resource & get() const ENTT_NOEXCEPT { - ENTT_ASSERT(static_cast(resource)); - return *resource; - } - - /*! @copydoc get */ - Resource & get() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).get()); - } - - /*! @copydoc get */ - inline operator const Resource & () const ENTT_NOEXCEPT { return get(); } - - /*! @copydoc get */ - inline operator Resource & () ENTT_NOEXCEPT { return get(); } - - /*! @copydoc get */ - inline const Resource & operator *() const ENTT_NOEXCEPT { return get(); } - - /*! @copydoc get */ - inline Resource & operator *() ENTT_NOEXCEPT { return get(); } - - /** - * @brief Gets a pointer to the managed resource. - * - * @warning - * The behavior is undefined if the handle doesn't contain a resource.
- * An assertion will abort the execution at runtime in debug mode if the - * handle is empty. - * - * @return A pointer to the managed resource or `nullptr` if the handle - * contains no resource at all. - */ - inline const Resource * operator->() const ENTT_NOEXCEPT { - ENTT_ASSERT(static_cast(resource)); - return resource.get(); - } - - /*! @copydoc operator-> */ - inline Resource * operator->() ENTT_NOEXCEPT { - return const_cast(std::as_const(*this).operator->()); - } - - /** - * @brief Returns true if a handle contains a resource, false otherwise. - * @return True if the handle contains a resource, false otherwise. - */ - explicit operator bool() const { return static_cast(resource); } - -private: - std::shared_ptr resource; -}; - - -} - - -#endif // ENTT_RESOURCE_HANDLE_HPP diff --git a/modules/entt/src/entt/resource/loader.hpp b/modules/entt/src/entt/resource/loader.hpp index e36fbd8..25a7b34 100644 --- a/modules/entt/src/entt/resource/loader.hpp +++ b/modules/entt/src/entt/resource/loader.hpp @@ -1,65 +1,33 @@ -#ifndef ENTT_RESOURCE_LOADER_HPP -#define ENTT_RESOURCE_LOADER_HPP - +#ifndef ENTT_RESOURCE_LOADEr_HPP +#define ENTT_RESOURCE_LOADEr_HPP #include +#include #include "fwd.hpp" - namespace entt { - /** - * @brief Base class for resource loaders. - * - * Resource loaders must inherit from this class and stay true to the CRTP - * idiom. Moreover, a resource loader must expose a public, const member - * function named `load` that accepts a variable number of arguments and returns - * a shared pointer to the resource just created.
- * As an example: - * - * @code{.cpp} - * struct my_resource {}; - * - * struct my_loader: entt::resource_loader { - * std::shared_ptr load(int) const { - * // use the integer value somehow - * return std::make_shared(); - * } - * }; - * @endcode - * - * In general, resource loaders should not have a state or retain data of any - * type. They should let the cache manage their resources instead. - * - * @note - * Base class and CRTP idiom aren't strictly required with the current - * implementation. One could argue that a cache can easily work with loaders of - * any type. However, future changes won't be breaking ones by forcing the use - * of a base class today and that's why the model is already in its place. - * - * @tparam Loader Type of the derived class. - * @tparam Resource Type of resource for which to use the loader. + * @brief Transparent loader for shared resources. + * @tparam Type Type of resources created by the loader. */ -template -class resource_loader { - /*! @brief Resource loaders are friends of their caches. */ - friend class resource_cache; +template +struct resource_loader { + /*! @brief Result type. */ + using result_type = std::shared_ptr; /** - * @brief Loads the resource and returns it. - * @tparam Args Types of arguments for the loader. - * @param args Arguments for the loader. - * @return The resource just loaded or an empty pointer in case of errors. + * @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 - std::shared_ptr get(Args &&... args) const { - return static_cast(this)->load(std::forward(args)...); + result_type operator()(Args &&...args) const { + return std::make_shared(std::forward(args)...); } }; +} // namespace entt -} - - -#endif // ENTT_RESOURCE_LOADER_HPP +#endif diff --git a/modules/entt/src/entt/resource/resource.hpp b/modules/entt/src/entt/resource/resource.hpp new file mode 100644 index 0000000..31c7ec8 --- /dev/null +++ b/modules/entt/src/entt/resource/resource.hpp @@ -0,0 +1,242 @@ +#ifndef ENTT_RESOURCE_RESOURCE_HPP +#define ENTT_RESOURCE_RESOURCE_HPP + +#include +#include +#include +#include "../config/config.h" +#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 { + /*! @brief Resource handles are friends with each other. */ + template + friend class resource; + + template + static constexpr bool is_acceptable_v = !std::is_same_v && std::is_constructible_v; + +public: + /*! @brief Default constructor. */ + resource() ENTT_NOEXCEPT + : value{} {} + + /** + * @brief Creates a handle from a weak pointer, namely a resource. + * @param res A weak pointer to a resource. + */ + explicit resource(std::shared_ptr res) ENTT_NOEXCEPT + : value{std::move(res)} {} + + /*! @brief Default copy constructor. */ + resource(const resource &) ENTT_NOEXCEPT = default; + + /*! @brief Default move constructor. */ + resource(resource &&) ENTT_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, Type &res) ENTT_NOEXCEPT + : value{other.value, std::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>> + resource(const resource &other) ENTT_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>> + resource(resource &&other) ENTT_NOEXCEPT + : value{std::move(other.value)} {} + + /** + * @brief Default copy assignment operator. + * @return This resource handle. + */ + resource &operator=(const resource &) ENTT_NOEXCEPT = default; + + /** + * @brief Default move assignment operator. + * @return This resource handle. + */ + resource &operator=(resource &&) ENTT_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 + std::enable_if_t, resource &> + operator=(const resource &other) ENTT_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 + std::enable_if_t, resource &> + operator=(resource &&other) ENTT_NOEXCEPT { + value = std::move(other.value); + return *this; + } + + /** + * @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]] Type &operator*() const ENTT_NOEXCEPT { + return *value; + } + + /*! @copydoc operator* */ + [[nodiscard]] operator Type &() const ENTT_NOEXCEPT { + return *value; + } + + /** + * @brief Returns a pointer to the managed resource. + * @return A pointer to the managed resource. + */ + [[nodiscard]] Type *operator->() const ENTT_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 ENTT_NOEXCEPT { + return static_cast(value); + } + + /** + * @brief Returns the number of handles pointing the same resource. + * @return The number of handles pointing the same resource. + */ + [[nodiscard]] long use_count() const ENTT_NOEXCEPT { + return value.use_count(); + } + +private: + std::shared_ptr value; +}; + +/** + * @brief Compares two handles. + * @tparam Res Type of resource managed by the first handle. + * @tparam Other Type of resource managed by the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return True if both handles refer to the same resource, false otherwise. + */ +template +[[nodiscard]] bool operator==(const resource &lhs, const resource &rhs) ENTT_NOEXCEPT { + return (std::addressof(*lhs) == std::addressof(*rhs)); +} + +/** + * @brief Compares two handles. + * @tparam Res Type of resource managed by the first handle. + * @tparam Other Type of resource managed by the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return False if both handles refer to the same registry, true otherwise. + */ +template +[[nodiscard]] bool operator!=(const resource &lhs, const resource &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +/** + * @brief Compares two handles. + * @tparam Res Type of resource managed by the first handle. + * @tparam Other Type of resource managed by the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return True if the first handle is less than the second, false otherwise. + */ +template +[[nodiscard]] bool operator<(const resource &lhs, const resource &rhs) ENTT_NOEXCEPT { + return (std::addressof(*lhs) < std::addressof(*rhs)); +} + +/** + * @brief Compares two handles. + * @tparam Res Type of resource managed by the first handle. + * @tparam Other Type of resource managed by the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return True if the first handle is greater than the second, false otherwise. + */ +template +[[nodiscard]] bool operator>(const resource &lhs, const resource &rhs) ENTT_NOEXCEPT { + return (std::addressof(*lhs) > std::addressof(*rhs)); +} + +/** + * @brief Compares two handles. + * @tparam Res Type of resource managed by the first handle. + * @tparam Other Type of resource managed by the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return True if the first handle is less than or equal to the second, false + * otherwise. + */ +template +[[nodiscard]] bool operator<=(const resource &lhs, const resource &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +/** + * @brief Compares two handles. + * @tparam Res Type of resource managed by the first handle. + * @tparam Other Type of resource managed by the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return True if the first handle is greater than or equal to the second, + * false otherwise. + */ +template +[[nodiscard]] bool operator>=(const resource &lhs, const resource &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +} // namespace entt + +#endif diff --git a/modules/entt/src/entt/signal/delegate.hpp b/modules/entt/src/entt/signal/delegate.hpp index 40e509c..971b687 100644 --- a/modules/entt/src/entt/signal/delegate.hpp +++ b/modules/entt/src/entt/signal/delegate.hpp @@ -1,60 +1,61 @@ #ifndef ENTT_SIGNAL_DELEGATE_HPP #define ENTT_SIGNAL_DELEGATE_HPP - -#include -#include +#include #include +#include #include +#include #include "../config/config.h" - +#include "../core/type_traits.hpp" +#include "fwd.hpp" namespace entt { - /** * @cond TURN_OFF_DOXYGEN * Internal details not to be documented. */ - namespace internal { - template -auto to_function_pointer(Ret(*)(Args...)) -> Ret(*)(Args...); - +auto function_pointer(Ret (*)(Args...)) -> Ret (*)(Args...); -template -auto to_function_pointer(Ret(*)(Type *, Args...), Type *) -> Ret(*)(Args...); +template +auto function_pointer(Ret (*)(Type, Args...), Other &&) -> Ret (*)(Args...); +template +auto function_pointer(Ret (Class::*)(Args...), Other &&...) -> Ret (*)(Args...); -template -auto to_function_pointer(Ret(Class:: *)(Args...), Class *) -> Ret(*)(Args...); +template +auto function_pointer(Ret (Class::*)(Args...) const, Other &&...) -> Ret (*)(Args...); +template +auto function_pointer(Type Class::*, Other &&...) -> Type (*)(); -template -auto to_function_pointer(Ret(Class:: *)(Args...) const, Class *) -> Ret(*)(Args...); - +template +using function_pointer_t = decltype(internal::function_pointer(std::declval()...)); +template +[[nodiscard]] constexpr auto index_sequence_for(Ret (*)(Args...)) { + return std::index_sequence_for{}; } +} // namespace internal /** * Internal details not to be documented. - * @endcond TURN_OFF_DOXYGEN + * @endcond */ - /*! @brief Used to wrap a function or a member of a specified type. */ template struct connect_arg_t {}; - /*! @brief Constant of type connect_arg_t used to disambiguate calls. */ template -constexpr connect_arg_t connect_arg{}; - +inline constexpr connect_arg_t connect_arg{}; /** * @brief Basic delegate implementation. @@ -65,105 +66,179 @@ constexpr connect_arg_t connect_arg{}; 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 general purpose invoker with no memory overhead for - * free functions (with or without payload) and members provided along with an - * instance on which to invoke 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 proto_fn_type = Ret(const void *, Args...); + template + [[nodiscard]] auto wrap(std::index_sequence) ENTT_NOEXCEPT { + return [](const void *, Args... args) -> Ret { + [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); + return static_cast(std::invoke(Candidate, std::forward>>(std::get(arguments))...)); + }; + } + + template + [[nodiscard]] auto wrap(Type &, std::index_sequence) ENTT_NOEXCEPT { + return [](const void *payload, Args... args) -> Ret { + [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); + Type *curr = static_cast(const_cast *>(payload)); + return static_cast(std::invoke(Candidate, *curr, std::forward>>(std::get(arguments))...)); + }; + } + + template + [[nodiscard]] auto wrap(Type *, std::index_sequence) ENTT_NOEXCEPT { + return [](const void *payload, Args... args) -> Ret { + [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward(args)...); + Type *curr = static_cast(const_cast *>(payload)); + return static_cast(std::invoke(Candidate, curr, std::forward>>(std::get(arguments))...)); + }; + } public: + /*! @brief Function type of the contained target. */ + using function_type = Ret(const void *, Args...); /*! @brief Function type of the delegate. */ - using function_type = Ret(Args...); + using type = Ret(Args...); + /*! @brief Return type of the delegate. */ + using result_type = Ret; /*! @brief Default constructor. */ delegate() ENTT_NOEXCEPT - : fn{nullptr}, data{nullptr} - {} + : instance{nullptr}, + fn{nullptr} {} /** - * @brief Constructs a delegate and connects a free function to it. - * @tparam Function A valid free function pointer. + * @brief Constructs a delegate and connects a free function or an unbound + * member. + * @tparam Candidate Function or member to connect to the delegate. */ - template - delegate(connect_arg_t) ENTT_NOEXCEPT - : delegate{} - { - connect(); + template + delegate(connect_arg_t) ENTT_NOEXCEPT { + connect(); } /** - * @brief Constructs a delegate and connects a member for a given instance - * or a free function with payload. - * @tparam Candidate Member or free function to connect to the delegate. + * @brief Constructs a delegate and connects a free function with payload or + * a bound member. + * @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. + * @param value_or_instance A valid object that fits the purpose. */ template - delegate(connect_arg_t, Type *value_or_instance) ENTT_NOEXCEPT - : delegate{} - { - connect(value_or_instance); + delegate(connect_arg_t, Type &&value_or_instance) ENTT_NOEXCEPT { + connect(std::forward(value_or_instance)); } /** - * @brief Connects a free function to a delegate. - * @tparam Function A valid free function pointer. + * @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. */ - template - void connect() ENTT_NOEXCEPT { - static_assert(std::is_invocable_r_v); - data = nullptr; + delegate(function_type *function, const void *payload = nullptr) ENTT_NOEXCEPT { + connect(function, payload); + } - fn = [](const void *, Args... args) -> Ret { - // this allows void(...) to eat return values and avoid errors - return Ret(std::invoke(Function, args...)); - }; + /** + * @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() ENTT_NOEXCEPT { + instance = nullptr; + + if constexpr(std::is_invocable_r_v) { + fn = [](const void *, Args... args) -> Ret { + return Ret(std::invoke(Candidate, std::forward(args)...)); + }; + } else if constexpr(std::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 member function for a given instance or a free function - * with payload to a delegate. + * @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.
+ * 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 Member or free function to connect to the delegate. + * @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) ENTT_NOEXCEPT { + instance = &value_or_instance; + + if constexpr(std::is_invocable_r_v) { + fn = [](const void *payload, Args... args) -> Ret { + Type *curr = static_cast(const_cast *>(payload)); + return Ret(std::invoke(Candidate, *curr, std::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) ENTT_NOEXCEPT { - static_assert(std::is_invocable_r_v); - data = value_or_instance; - - fn = [](const void *payload, Args... args) -> Ret { - Type *curr = nullptr; - - if constexpr(std::is_const_v) { - curr = static_cast(payload); - } else { - curr = static_cast(const_cast(payload)); - } + instance = value_or_instance; + + if constexpr(std::is_invocable_r_v) { + fn = [](const void *payload, Args... args) -> Ret { + Type *curr = static_cast(const_cast *>(payload)); + return Ret(std::invoke(Candidate, curr, std::forward(args)...)); + }; + } else { + fn = wrap(value_or_instance, internal::index_sequence_for(internal::function_pointer_t{})); + } + } - // this allows void(...) to eat return values and avoid errors - return Ret(std::invoke(Candidate, curr, args...)); - }; + /** + * @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) ENTT_NOEXCEPT { + instance = payload; + fn = function; } /** @@ -172,16 +247,16 @@ public: * After a reset, a delegate cannot be invoked anymore. */ void reset() ENTT_NOEXCEPT { + instance = nullptr; fn = nullptr; - data = nullptr; } /** - * @brief Returns the instance linked to a delegate, if any. - * @return An opaque pointer to the instance linked to the delegate, if any. + * @brief Returns the instance or the payload linked to a delegate, if any. + * @return An opaque pointer to the underlying data. */ - const void * instance() const ENTT_NOEXCEPT { - return data; + [[nodiscard]] const void *data() const ENTT_NOEXCEPT { + return instance; } /** @@ -191,92 +266,75 @@ public: * * @warning * Attempting to trigger an invalid delegate results in undefined - * behavior.
- * An assertion will abort the execution at runtime in debug mode if the - * delegate has not yet been set. + * 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(fn); - return fn(data, args...); + ENTT_ASSERT(static_cast(*this), "Uninitialized delegate"); + return fn(instance, std::forward(args)...); } /** * @brief Checks whether a delegate actually stores a listener. * @return False if the delegate is empty, true otherwise. */ - explicit operator bool() const ENTT_NOEXCEPT { - // no need to test also data - return fn; + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + // no need to also test instance + return !(fn == nullptr); } /** - * @brief Checks if the connected functions differ. - * - * Instances connected to delegates are ignored by this operator. Use the - * `instance` member function instead. - * + * @brief Compares the contents of two delegates. * @param other Delegate with which to compare. - * @return False if the connected functions differ, true otherwise. + * @return False if the two contents differ, true otherwise. */ - bool operator==(const delegate &other) const ENTT_NOEXCEPT { - return fn == other.fn; + [[nodiscard]] bool operator==(const delegate &other) const ENTT_NOEXCEPT { + return fn == other.fn && instance == other.instance; } private: - proto_fn_type *fn; - const void *data; + const void *instance; + function_type *fn; }; - /** - * @brief Checks if the connected functions differ. - * - * Instances connected to delegates are ignored by this operator. Use the - * `instance` member function instead. - * + * @brief Compares the contents of two delegates. * @tparam Ret Return type of a function type. * @tparam Args Types of arguments of a function type. * @param lhs A valid delegate object. * @param rhs A valid delegate object. - * @return True if the connected functions differ, false otherwise. + * @return True if the two contents differ, false otherwise. */ template -bool operator!=(const delegate &lhs, const delegate &rhs) ENTT_NOEXCEPT { +[[nodiscard]] bool operator!=(const delegate &lhs, const delegate &rhs) ENTT_NOEXCEPT { return !(lhs == rhs); } - /** * @brief Deduction guide. - * - * It allows to deduce the function type of the delegate directly from a - * function provided to the constructor. - * - * @tparam Function A valid free function pointer. + * @tparam Candidate Function or member to connect to the delegate. */ -template -delegate(connect_arg_t) ENTT_NOEXCEPT --> delegate>; - +template +delegate(connect_arg_t) -> delegate>>; /** * @brief Deduction guide. - * - * It allows to deduce the function type of the delegate directly from a member - * or a free function with payload provided to the constructor. - * - * @tparam Candidate Member or free function to connect to the delegate. + * @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 *) ENTT_NOEXCEPT --> delegate()))>>; - +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 // ENTT_SIGNAL_DELEGATE_HPP +#endif diff --git a/modules/entt/src/entt/signal/dispatcher.hpp b/modules/entt/src/entt/signal/dispatcher.hpp index 5fe1bab..448aea7 100644 --- a/modules/entt/src/entt/signal/dispatcher.hpp +++ b/modules/entt/src/entt/signal/dispatcher.hpp @@ -1,19 +1,104 @@ #ifndef ENTT_SIGNAL_DISPATCHER_HPP #define ENTT_SIGNAL_DISPATCHER_HPP - -#include +#include +#include #include -#include #include +#include +#include #include "../config/config.h" -#include "../core/family.hpp" -#include "../core/type_traits.hpp" +#include "../container/dense_map.hpp" +#include "../core/compressed_pair.hpp" +#include "../core/fwd.hpp" +#include "../core/type_info.hpp" +#include "../core/utility.hpp" +#include "fwd.hpp" #include "sigh.hpp" - namespace entt { +/** + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. + */ + +namespace internal { + +struct basic_dispatcher_handler { + virtual ~basic_dispatcher_handler() = default; + virtual void publish() = 0; + virtual void disconnect(void *) = 0; + virtual void clear() ENTT_NOEXCEPT = 0; + virtual std::size_t size() const ENTT_NOEXCEPT = 0; +}; + +template +class dispatcher_handler final: public basic_dispatcher_handler { + static_assert(std::is_same_v>, "Invalid event type"); + + using alloc_traits = std::allocator_traits; + using signal_type = sigh>; + using container_type = std::vector>; + +public: + using allocator_type = Allocator; + + dispatcher_handler(const allocator_type &allocator) + : signal{allocator}, + events{allocator} {} + + void publish() override { + const auto length = events.size(); + + for(std::size_t pos{}; pos < length; ++pos) { + signal.publish(events[pos]); + } + + events.erase(events.cbegin(), events.cbegin() + length); + } + + void disconnect(void *instance) override { + bucket().disconnect(instance); + } + + void clear() ENTT_NOEXCEPT override { + events.clear(); + } + + [[nodiscard]] auto bucket() ENTT_NOEXCEPT { + using sink_type = typename sigh::sink_type; + return sink_type{signal}; + } + + void trigger(Event event) { + signal.publish(event); + } + + template + void enqueue(Args &&...args) { + if constexpr(std::is_aggregate_v) { + events.push_back(Event{std::forward(args)...}); + } else { + events.emplace_back(std::forward(args)...); + } + } + + std::size_t size() const ENTT_NOEXCEPT override { + return events.size(); + } + +private: + signal_type signal; + container_type events; +}; + +} // namespace internal + +/** + * Internal details not to be documented. + * @endcond + */ /** * @brief Basic dispatcher implementation. @@ -22,117 +107,142 @@ namespace entt { * events to be published all together once per tick.
* Listeners are provided in the form of member functions. For each event of * type `Event`, listeners are such that they can be invoked with an argument of - * type `const Event &`, no matter what the return type is. + * type `Event &`, 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. * - * The type of the instances is `Class *` (a naked pointer). It means that users - * must guarantee that the lifetimes of the instances overcome the one of the - * dispatcher itself to avoid crashes. + * @tparam Allocator Type of allocator used to manage memory and elements. */ -class dispatcher { - using event_family = family; +template +class basic_dispatcher { + template + using handler_type = internal::dispatcher_handler; - template - using instance_type = typename sigh::template instance_type; + using key_type = id_type; + // std::shared_ptr because of its type erased allocator which is pretty useful here + using mapped_type = std::shared_ptr; - struct base_wrapper { - virtual ~base_wrapper() = default; - virtual void publish() = 0; - }; + using alloc_traits = std::allocator_traits; + using container_allocator = typename alloc_traits::template rebind_alloc>; + using container_type = dense_map, container_allocator>; template - struct signal_wrapper: base_wrapper { - using signal_type = sigh; - using sink_type = typename signal_type::sink_type; - - void publish() override { - for(const auto &event: events[current]) { - signal.publish(event); - } + [[nodiscard]] handler_type &assure(const id_type id) { + auto &&ptr = pools.first()[id]; - events[current++].clear(); - current %= std::extent::value; + if(!ptr) { + const auto &allocator = pools.second(); + ptr = std::allocate_shared>(allocator, allocator); } - inline sink_type sink() ENTT_NOEXCEPT { - return signal.sink(); - } + return static_cast &>(*ptr); + } - template - inline void trigger(Args &&... args) { - signal.publish({ std::forward(args)... }); - } + template + [[nodiscard]] const handler_type *assure(const id_type id) const { + auto &container = pools.first(); - template - inline void enqueue(Args &&... args) { - events[current].emplace_back(std::forward(args)...); + if(const auto it = container.find(id); it != container.end()) { + return static_cast *>(it->second.get()); } - private: - signal_type signal{}; - std::vector events[2]; - int current{}; - }; - - struct wrapper_data { - std::unique_ptr wrapper; - ENTT_ID_TYPE runtime_type; - }; - - template - static auto type() ENTT_NOEXCEPT { - if constexpr(is_named_type_v) { - return named_type_traits::value; - } else { - return event_family::type; - } + return nullptr; } - template - signal_wrapper & assure() { - const auto wtype = type(); - wrapper_data *wdata = nullptr; +public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; - if constexpr(is_named_type_v) { - const auto it = std::find_if(wrappers.begin(), wrappers.end(), [wtype](const auto &candidate) { - return candidate.wrapper && candidate.runtime_type == wtype; - }); + /*! @brief Default constructor. */ + basic_dispatcher() + : basic_dispatcher{allocator_type{}} {} - wdata = (it == wrappers.cend() ? &wrappers.emplace_back() : &(*it)); - } else { - if(!(wtype < wrappers.size())) { - wrappers.resize(wtype+1); - } + /** + * @brief Constructs a dispatcher with a given allocator. + * @param allocator The allocator to use. + */ + explicit basic_dispatcher(const allocator_type &allocator) + : pools{allocator, allocator} {} - wdata = &wrappers[wtype]; + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_dispatcher(basic_dispatcher &&other) ENTT_NOEXCEPT + : pools{std::move(other.pools)} {} - if(wdata->wrapper && wdata->runtime_type != wtype) { - wrappers.emplace_back(); - std::swap(wrappers[wtype], wrappers.back()); - wdata = &wrappers[wtype]; - } - } + /** + * @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) ENTT_NOEXCEPT + : pools{container_type{std::move(other.pools.first()), allocator}, allocator} {} - if(!wdata->wrapper) { - wdata->wrapper = std::make_unique>(); - wdata->runtime_type = wtype; - } + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This dispatcher. + */ + basic_dispatcher &operator=(basic_dispatcher &&other) ENTT_NOEXCEPT { + pools = std::move(other.pools); + return *this; + } - return static_cast &>(*wdata->wrapper); + /** + * @brief Exchanges the contents with those of a given dispatcher. + * @param other Dispatcher to exchange the content with. + */ + void swap(basic_dispatcher &other) { + using std::swap; + swap(pools, other.pools); } -public: - /*! @brief Type of sink for the given event. */ + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return pools.second(); + } + + /** + * @brief Returns the number of pending events for a given type. + * @tparam Event 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 - using sink_type = typename signal_wrapper::sink_type; + size_type size(const id_type id = type_hash::value()) const ENTT_NOEXCEPT { + const auto *cpool = assure(id); + return cpool ? cpool->size() : 0u; + } /** - * @brief Returns a sink object for the given event. + * @brief Returns the total number of pending events. + * @return The total number of pending events. + */ + size_type size() const ENTT_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: + * The function type for a listener is _compatible_ with: * @code{.cpp} - * void(const Event &); + * void(Event &); * @endcode * * The order of invocation of the listeners isn't guaranteed. @@ -140,108 +250,141 @@ public: * @sa sink * * @tparam Event 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 - inline sink_type sink() ENTT_NOEXCEPT { - return assure().sink(); + [[nodiscard]] auto sink(const id_type id = type_hash::value()) { + return assure(id).bucket(); } /** - * @brief Triggers an immediate event of the given type. - * - * All the listeners registered for the given type are immediately notified. - * The event is discarded after the execution. - * + * @brief Triggers an immediate event of a given type. + * @tparam Event Type of event to trigger. + * @param event An instance of the given type of event. + */ + template + void trigger(Event &&event = {}) { + trigger(type_hash>::value(), std::forward(event)); + } + + /** + * @brief Triggers an immediate event on a queue of a given type. * @tparam Event Type of event to trigger. + * @param event 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, Event &&event = {}) { + assure>(id).trigger(std::forward(event)); + } + + /** + * @brief Enqueues an event of the given type. + * @tparam Event 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 - inline void trigger(Args &&... args) { - assure().trigger(std::forward(args)...); + void enqueue(Args &&...args) { + enqueue_hint(type_hash::value(), std::forward(args)...); } /** - * @brief Triggers an immediate event of the given type. - * - * All the listeners registered for the given type are immediately notified. - * The event is discarded after the execution. - * - * @tparam Event Type of event to trigger. + * @brief Enqueues an event of the given type. + * @tparam Event Type of event to enqueue. * @param event An instance of the given type of event. */ template - inline void trigger(Event &&event) { - assure>().trigger(std::forward(event)); + void enqueue(Event &&event) { + enqueue_hint(type_hash>::value(), std::forward(event)); } /** * @brief Enqueues an event of the given type. - * - * An event of the given type is queued. No listener is invoked. Use the - * `update` member function to notify listeners when ready. - * * @tparam Event 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 - inline void enqueue(Args &&... args) { - assure().enqueue(std::forward(args)...); + void enqueue_hint(const id_type id, Args &&...args) { + assure(id).enqueue(std::forward(args)...); } /** * @brief Enqueues an event of the given type. - * - * An event of the given type is queued. No listener is invoked. Use the - * `update` member function to notify listeners when ready. - * * @tparam Event Type of event to enqueue. + * @param id Name used to map the event queue within the dispatcher. * @param event An instance of the given type of event. */ template - inline void enqueue(Event &&event) { - assure>().enqueue(std::forward(event)); + void enqueue_hint(const id_type id, Event &&event) { + assure>(id).enqueue(std::forward(event)); } /** - * @brief Delivers all the pending events of the given type. - * - * This method is blocking and it doesn't return until all the events are - * delivered to the registered listeners. It's responsibility of the users - * to reduce at a minimum the time spent in the bodies of the listeners. - * - * @tparam Event Type of events to send. + * @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 Event Type of event to discard. + * @param id Name used to map the event queue within the dispatcher. */ template - inline void update() { - assure().publish(); + void clear(const id_type id = type_hash::value()) { + assure(id).clear(); + } + + /*! @brief Discards all the events queued so far. */ + void clear() ENTT_NOEXCEPT { + for(auto &&cpool: pools.first()) { + cpool.second->clear(); + } } /** - * @brief Delivers all the pending events. - * - * This method is blocking and it doesn't return until all the events are - * delivered to the registered listeners. It's responsibility of the users - * to reduce at a minimum the time spent in the bodies of the listeners. + * @brief Delivers all the pending events of a given queue. + * @tparam Event Type of event to send. + * @param id Name used to map the event queue within the dispatcher. */ - inline void update() const { - for(auto pos = wrappers.size(); pos; --pos) { - auto &wdata = wrappers[pos-1]; + template + void update(const id_type id = type_hash::value()) { + assure(id).publish(); + } - if(wdata.wrapper) { - wdata.wrapper->publish(); - } + /*! @brief Delivers all the pending events. */ + void update() const { + for(auto &&cpool: pools.first()) { + cpool.second->publish(); } } private: - std::vector wrappers; + compressed_pair pools; }; +} // namespace entt -} - - -#endif // ENTT_SIGNAL_DISPATCHER_HPP +#endif diff --git a/modules/entt/src/entt/signal/emitter.hpp b/modules/entt/src/entt/signal/emitter.hpp index 1abb506..fee36da 100644 --- a/modules/entt/src/entt/signal/emitter.hpp +++ b/modules/entt/src/entt/signal/emitter.hpp @@ -1,22 +1,22 @@ #ifndef ENTT_SIGNAL_EMITTER_HPP #define ENTT_SIGNAL_EMITTER_HPP - -#include -#include #include -#include -#include -#include +#include +#include #include +#include +#include +#include #include "../config/config.h" -#include "../core/family.hpp" -#include "../core/type_traits.hpp" - +#include "../container/dense_map.hpp" +#include "../core/fwd.hpp" +#include "../core/type_info.hpp" +#include "../core/utility.hpp" +#include "fwd.hpp" namespace entt { - /** * @brief General purpose event emitter. * @@ -29,59 +29,63 @@ namespace entt { * } * @endcode * - * Handlers for the type of events are created internally on the fly. It's not + * Pools for the type of events are created internally on the fly. It's not * required to specify in advance the full list of accepted types.
* Moreover, whenever an event is published, an emitter provides the listeners - * with a reference to itself along with a const reference to the event. - * Therefore listeners have an handy way to work with it without incurring in - * the need of capturing a reference to the emitter. + * with a reference to itself along with a reference to the event. Therefore + * listeners have an handy way to work with it without incurring in the need of + * capturing a reference to the emitter. * * @tparam Derived Actual type of emitter that extends the class template. */ template class emitter { - using handler_family = family; - - struct base_handler { - virtual ~base_handler() = default; + struct basic_pool { + virtual ~basic_pool() = default; virtual bool empty() const ENTT_NOEXCEPT = 0; virtual void clear() ENTT_NOEXCEPT = 0; }; template - struct event_handler: base_handler { - using listener_type = std::function; + struct pool_handler final: basic_pool { + static_assert(std::is_same_v>, "Invalid event type"); + + using listener_type = std::function; using element_type = std::pair; using container_type = std::list; using connection_type = typename container_type::iterator; - bool empty() const ENTT_NOEXCEPT override { + [[nodiscard]] bool empty() const ENTT_NOEXCEPT override { auto pred = [](auto &&element) { return element.first; }; - return std::all_of(once_list.cbegin(), once_list.cend(), pred) && - std::all_of(on_list.cbegin(), on_list.cend(), pred); + return std::all_of(once_list.cbegin(), once_list.cend(), pred) + && std::all_of(on_list.cbegin(), on_list.cend(), pred); } void clear() ENTT_NOEXCEPT override { if(publishing) { - auto func = [](auto &&element) { element.first = true; }; - std::for_each(once_list.begin(), once_list.end(), func); - std::for_each(on_list.begin(), on_list.end(), func); + for(auto &&element: once_list) { + element.first = true; + } + + for(auto &&element: on_list) { + element.first = true; + } } else { once_list.clear(); on_list.clear(); } } - inline connection_type once(listener_type listener) { + connection_type once(listener_type listener) { return once_list.emplace(once_list.cend(), false, std::move(listener)); } - inline connection_type on(listener_type listener) { + connection_type on(listener_type listener) { return on_list.emplace(on_list.cend(), false, std::move(listener)); } - void erase(connection_type conn) ENTT_NOEXCEPT { + void erase(connection_type conn) { conn->first = true; if(!publishing) { @@ -91,18 +95,19 @@ class emitter { } } - void publish(const Event &event, Derived &ref) { + void publish(Event &event, Derived &ref) { container_type swap_list; once_list.swap(swap_list); - auto func = [&event, &ref](auto &&element) { - return element.first ? void() : element.second(event, ref); - }; - publishing = true; - std::for_each(on_list.rbegin(), on_list.rend(), func); - std::for_each(swap_list.rbegin(), swap_list.rend(), func); + for(auto &&element: on_list) { + element.first ? void() : element.second(event, ref); + } + + for(auto &&element: swap_list) { + element.first ? void() : element.second(event, ref); + } publishing = false; @@ -115,57 +120,27 @@ class emitter { container_type on_list{}; }; - struct handler_data { - std::unique_ptr handler; - ENTT_ID_TYPE runtime_type; - }; - template - static auto type() ENTT_NOEXCEPT { - if constexpr(is_named_type_v) { - return named_type_traits::value; + [[nodiscard]] pool_handler *assure() { + if(auto &&ptr = pools[type_hash::value()]; !ptr) { + auto *cpool = new pool_handler{}; + ptr.reset(cpool); + return cpool; } else { - return handler_family::type; + return static_cast *>(ptr.get()); } } template - event_handler * assure() const ENTT_NOEXCEPT { - const auto htype = type(); - handler_data *hdata = nullptr; - - if constexpr(is_named_type_v) { - const auto it = std::find_if(handlers.begin(), handlers.end(), [htype](const auto &candidate) { - return candidate.handler && candidate.runtime_type == htype; - }); - - hdata = (it == handlers.cend() ? &handlers.emplace_back() : &(*it)); - } else { - if(!(htype < handlers.size())) { - handlers.resize(htype+1); - } - - hdata = &handlers[htype]; - - if(hdata->handler && hdata->runtime_type != htype) { - handlers.emplace_back(); - std::swap(handlers[htype], handlers.back()); - hdata = &handlers[htype]; - } - } - - if(!hdata->handler) { - hdata->handler = std::make_unique>(); - hdata->runtime_type = htype; - } - - return static_cast *>(hdata->handler.get()); + [[nodiscard]] const pool_handler *assure() const { + const auto it = pools.find(type_hash::value()); + return (it == pools.cend()) ? nullptr : static_cast *>(it->second.get()); } public: /** @brief Type of listeners accepted for the given event. */ template - using listener = typename event_handler::listener_type; + using listener = typename pool_handler::listener_type; /** * @brief Generic connection type for events. @@ -177,7 +152,7 @@ public: * @tparam Event Type of event for which the connection is created. */ template - struct connection: private event_handler::connection_type { + struct connection: private pool_handler::connection_type { /** @brief Event emitters are friend classes of connections. */ friend class emitter; @@ -188,24 +163,23 @@ public: * @brief Creates a connection that wraps its underlying instance. * @param conn A connection object to wrap. */ - connection(typename event_handler::connection_type conn) - : event_handler::connection_type{std::move(conn)} - {} + connection(typename pool_handler::connection_type conn) + : pool_handler::connection_type{std::move(conn)} {} }; /*! @brief Default constructor. */ - emitter() ENTT_NOEXCEPT = default; + emitter() = default; /*! @brief Default destructor. */ virtual ~emitter() ENTT_NOEXCEPT { - static_assert(std::is_base_of_v, Derived>); + static_assert(std::is_base_of_v, Derived>, "Incorrect use of the class template"); } /*! @brief Default move constructor. */ emitter(emitter &&) = default; /*! @brief Default move assignment operator. @return This emitter. */ - emitter & operator=(emitter &&) = default; + emitter &operator=(emitter &&) = default; /** * @brief Emits the given event. @@ -219,8 +193,9 @@ public: * @param args Parameters to use to initialize the event. */ template - void publish(Args &&... args) { - assure()->publish({ std::forward(args)... }, *static_cast(this)); + void publish(Args &&...args) { + Event instance{std::forward(args)...}; + assure()->publish(instance, *static_cast(this)); } /** @@ -232,7 +207,7 @@ public: * to be used later to disconnect the listener if required. * * The listener is as a callable object that can be moved and the type of - * which is `void(const Event &, Derived &)`. + * which is _compatible_ with `void(Event &, Derived &)`. * * @note * Whenever an event is emitted, the emitter provides the listener with a @@ -257,7 +232,7 @@ public: * to be used later to disconnect the listener if required. * * The listener is as a callable object that can be moved and the type of - * which is `void(const Event &, Derived &)`. + * which is _compatible_ with `void(Event &, Derived &)`. * * @note * Whenever an event is emitted, the emitter provides the listener with a @@ -283,7 +258,7 @@ public: * @param conn A valid connection. */ template - void erase(connection conn) ENTT_NOEXCEPT { + void erase(connection conn) { assure()->erase(std::move(conn)); } @@ -296,7 +271,7 @@ public: * @tparam Event Type of event to reset. */ template - void clear() ENTT_NOEXCEPT { + void clear() { assure()->clear(); } @@ -307,9 +282,9 @@ public: * results in undefined behavior. */ void clear() ENTT_NOEXCEPT { - std::for_each(handlers.begin(), handlers.end(), [](auto &&hdata) { - return hdata.handler ? hdata.handler->clear() : void(); - }); + for(auto &&cpool: pools) { + cpool.second->clear(); + } } /** @@ -318,26 +293,25 @@ public: * @return True if there are no listeners registered, false otherwise. */ template - bool empty() const ENTT_NOEXCEPT { - return assure()->empty(); + [[nodiscard]] bool empty() const { + const auto *cpool = assure(); + return !cpool || cpool->empty(); } /** * @brief Checks if there are listeners registered with the event emitter. * @return True if there are no listeners registered, false otherwise. */ - bool empty() const ENTT_NOEXCEPT { - return std::all_of(handlers.cbegin(), handlers.cend(), [](auto &&hdata) { - return !hdata.handler || hdata.handler->empty(); + [[nodiscard]] bool empty() const ENTT_NOEXCEPT { + return std::all_of(pools.cbegin(), pools.cend(), [](auto &&cpool) { + return cpool.second->empty(); }); } private: - mutable std::vector handlers{}; + dense_map, identity> pools{}; }; +} // namespace entt -} - - -#endif // ENTT_SIGNAL_EMITTER_HPP +#endif diff --git a/modules/entt/src/entt/signal/fwd.hpp b/modules/entt/src/entt/signal/fwd.hpp index 0433bd3..59df1ea 100644 --- a/modules/entt/src/entt/signal/fwd.hpp +++ b/modules/entt/src/entt/signal/fwd.hpp @@ -1,27 +1,32 @@ #ifndef ENTT_SIGNAL_FWD_HPP #define ENTT_SIGNAL_FWD_HPP - -#include "../config/config.h" - +#include namespace entt { - -/*! @class delegate */ template class delegate; -/*! @class sink */ +template> +class basic_dispatcher; + template -class sink; +class emitter; -/*! @class sigh */ -template -struct sigh; +class connection; + +struct scoped_connection; + +template +class sink; +template> +class sigh; -} +/*! @brief Alias declaration for the most common use case. */ +using dispatcher = basic_dispatcher<>; +} // namespace entt -#endif // ENTT_SIGNAL_FWD_HPP +#endif diff --git a/modules/entt/src/entt/signal/sigh.hpp b/modules/entt/src/entt/signal/sigh.hpp index a037fcb..ffd220d 100644 --- a/modules/entt/src/entt/signal/sigh.hpp +++ b/modules/entt/src/entt/signal/sigh.hpp @@ -1,357 +1,608 @@ #ifndef ENTT_SIGNAL_SIGH_HPP #define ENTT_SIGNAL_SIGH_HPP - #include -#include -#include #include #include +#include +#include #include "../config/config.h" #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; /** - * @cond TURN_OFF_DOXYGEN - * Internal details not to be documented. + * @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 { + /*! @brief A sink is allowed to modify a signal. */ + friend class sink>; -namespace internal { + using alloc_traits = std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using container_type = std::vector, typename alloc_traits::template rebind_alloc>>; +public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Unsigned integer type. */ + using size_type = std::size_t; + /*! @brief Sink type. */ + using sink_type = sink>; -template -struct invoker; + /*! @brief Default constructor. */ + sigh() + : sigh{allocator_type{}} {} + /** + * @brief Constructs a signal handler with a given allocator. + * @param allocator The allocator to use. + */ + explicit sigh(const allocator_type &allocator) + : calls{allocator} {} -template -struct invoker { - virtual ~invoker() = default; + /** + * @brief Copy constructor. + * @param other The instance to copy from. + */ + sigh(const sigh &other) + : calls{other.calls} {} - bool invoke(Collector &collector, const delegate &delegate, Args... args) const { - return collector(delegate(args...)); - } -}; + /** + * @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) ENTT_NOEXCEPT + : calls{std::move(other.calls)} {} -template -struct invoker { - virtual ~invoker() = default; + /** + * @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) ENTT_NOEXCEPT + : calls{std::move(other.calls), allocator} {} - bool invoke(Collector &, const delegate &delegate, Args... args) const { - return (delegate(args...), true); + /** + * @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) ENTT_NOEXCEPT { + calls = std::move(other.calls); + return *this; + } -template -struct null_collector { - using result_type = Ret; - bool operator()(result_type) const ENTT_NOEXCEPT { return true; } -}; + /** + * @brief Exchanges the contents with those of a given signal handler. + * @param other Signal handler to exchange the content with. + */ + void swap(sigh &other) { + using std::swap; + swap(calls, other.calls); + } + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT { + return calls.get_allocator(); + } -template<> -struct null_collector { - using result_type = void; - bool operator()() const ENTT_NOEXCEPT { return true; } -}; + /** + * @brief Instance type when it comes to connecting member functions. + * @tparam Class Type of class to which the member function belongs. + */ + template + using instance_type = Class *; + /** + * @brief Number of listeners connected to the signal. + * @return Number of listeners currently connected. + */ + [[nodiscard]] size_type size() const ENTT_NOEXCEPT { + return calls.size(); + } -template -struct default_collector; + /** + * @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 ENTT_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 &&call: std::as_const(calls)) { + call(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 &&call: calls) { + if constexpr(std::is_void_v) { + if constexpr(std::is_invocable_r_v) { + call(args...); + if(func()) { break; } + } else { + call(args...); + func(); + } + } else { + if constexpr(std::is_invocable_r_v) { + if(func(call(args...))) { break; } + } else { + func(call(args...)); + } + } + } + } -template -struct default_collector { - using collector_type = null_collector; +private: + container_type calls; }; +/** + * @brief Connection class. + * + * Opaque object the aim of which is to allow users to release an already + * estabilished connection without having to keep a reference to the signal or + * the sink that generated it. + */ +class connection { + /*! @brief A sink is allowed to create connection objects. */ + template + friend class sink; -template -using default_collector_type = typename default_collector::collector_type; - + connection(delegate fn, void *ref) + : disconnect{fn}, signal{ref} {} -} +public: + /*! @brief Default constructor. */ + connection() = default; + /** + * @brief Checks whether a connection is properly initialized. + * @return True if the connection is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return static_cast(disconnect); + } -/** - * Internal details not to be documented. - * @endcond TURN_OFF_DOXYGEN - */ + /*! @brief Breaks the connection. */ + void release() { + if(disconnect) { + disconnect(signal); + disconnect.reset(); + } + } +private: + delegate disconnect; + void *signal{}; +}; /** - * @brief Sink implementation. - * - * Primary template isn't defined on purpose. All the specializations give a - * compile-time error unless the template parameter is a function type. + * @brief Scoped connection class. * - * @tparam Function A valid function type. + * Opaque object the aim of which is to allow users to release an already + * estabilished 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. */ -template -class sink; +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 Unmanaged signal handler declaration. - * - * Primary template isn't defined on purpose. All the specializations give a - * compile-time error unless the template parameter is a function type. - * - * @tparam Function A valid function type. - * @tparam Collector Type of collector to use, if any. - */ -template> -struct sigh; + /*! @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) ENTT_NOEXCEPT + : conn{std::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) ENTT_NOEXCEPT { + conn = std::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 = std::move(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 ENTT_NOEXCEPT { + return static_cast(conn); + } + + /*! @brief Breaks the connection. */ + void release() { + conn.release(); + } + +private: + connection conn; +}; /** - * @brief Sink implementation. + * @brief Sink class. * - * A sink is an opaque object used to connect listeners to signals.
+ * 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 a class. + * 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 { - /*! @brief A signal is allowed to create sinks. */ - template - friend struct sigh; +template +class sink> { + using signal_type = sigh; + using difference_type = typename signal_type::container_type::difference_type; - template - Type * payload_type(Ret(*)(Type *, Args...)); + template + static void release(Type value_or_instance, void *signal) { + sink{*static_cast(signal)}.disconnect(value_or_instance); + } - sink(std::vector> *ref) ENTT_NOEXCEPT - : calls{ref} - {} + template + static void release(void *signal) { + sink{*static_cast(signal)}.disconnect(); + } public: + /** + * @brief Constructs a sink that is allowed to modify a given signal. + * @param ref A valid reference to a signal object. + */ + sink(sigh &ref) ENTT_NOEXCEPT + : offset{}, + 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. */ - bool empty() const ENTT_NOEXCEPT { - return calls->empty(); + [[nodiscard]] bool empty() const ENTT_NOEXCEPT { + return signal->calls.empty(); } /** - * @brief Connects a free function to a signal. - * - * The signal handler performs checks to avoid multiple connections for free - * functions. - * + * @brief Returns a sink that connects before a given free function or an + * unbound member. * @tparam Function A valid free function pointer. + * @return A properly initialized sink object. */ template - void connect() { - disconnect(); - delegate delegate{}; - delegate.template connect(); - calls->emplace_back(std::move(delegate)); + [[nodiscard]] sink before() { + delegate call{}; + call.template connect(); + + const auto &calls = signal->calls; + const auto it = std::find(calls.cbegin(), calls.cend(), std::move(call)); + + sink other{*this}; + other.offset = calls.cend() - it; + return other; } /** - * @brief Connects a member function or a free function with payload 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 delegate. On the other side, the signal handler performs - * checks to avoid multiple connections for the same function.
- * 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 Member or free function to connect to the delegate. + * @brief Returns a sink that connects before a free function with payload + * or a bound member. + * @tparam Candidate Member or free function to look for. * @tparam Type Type of class or type of payload. - * @param value_or_instance A valid pointer that fits the purpose. + * @param value_or_instance A valid object that fits the purpose. + * @return A properly initialized sink object. */ template - void connect(Type *value_or_instance) { - if constexpr(std::is_member_function_pointer_v) { - disconnect(value_or_instance); - } else { - disconnect(); - } + [[nodiscard]] sink before(Type &&value_or_instance) { + delegate call{}; + call.template connect(value_or_instance); + + const auto &calls = signal->calls; + const auto it = std::find(calls.cbegin(), calls.cend(), std::move(call)); - delegate delegate{}; - delegate.template connect(value_or_instance); - calls->emplace_back(std::move(delegate)); + sink other{*this}; + other.offset = calls.cend() - it; + return other; } /** - * @brief Disconnects a free function from a signal. - * @tparam Function A valid free function pointer. + * @brief Returns a sink that connects before a given instance or specific + * payload. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid object that fits the purpose. + * @return A properly initialized sink object. */ - template - void disconnect() { - delegate delegate{}; + template + [[nodiscard]] sink before(Type &value_or_instance) { + return before(&value_or_instance); + } - if constexpr(std::is_invocable_r_v) { - delegate.template connect(); - } else { - decltype(payload_type(Function)) payload = nullptr; - delegate.template connect(payload); + /** + * @brief Returns a sink that connects before a given instance or specific + * payload. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid pointer that fits the purpose. + * @return A properly initialized sink object. + */ + template + [[nodiscard]] sink before(Type *value_or_instance) { + sink other{*this}; + + if(value_or_instance) { + const auto &calls = signal->calls; + const auto it = std::find_if(calls.cbegin(), calls.cend(), [value_or_instance](const auto &delegate) { + return delegate.data() == value_or_instance; + }); + + other.offset = calls.cend() - it; } - calls->erase(std::remove(calls->begin(), calls->end(), std::move(delegate)), calls->end()); + return other; } /** - * @brief Disconnects a given member function from a signal. - * @tparam Member Member function to disconnect from the signal. - * @tparam Class Type of class to which the member function belongs. - * @param instance A valid instance of type pointer to `Class`. - */ - template - void disconnect(Class *instance) { - static_assert(std::is_member_function_pointer_v); - delegate delegate{}; - delegate.template connect(instance); - calls->erase(std::remove_if(calls->begin(), calls->end(), [&delegate](const auto &other) { - return other == delegate && other.instance() == delegate.instance(); - }), calls->end()); + * @brief Returns a sink that connects before anything else. + * @return A properly initialized sink object. + */ + [[nodiscard]] sink before() { + sink other{*this}; + other.offset = signal->calls.size(); + return other; } /** - * @brief Disconnects all the listeners from a signal. + * @brief Connects a free function or an unbound member to a signal. + * + * The signal handler performs checks to avoid multiple connections for the + * same function. + * + * @tparam Candidate Function or member to connect to the signal. + * @return A properly initialized connection object. */ - void disconnect() { - calls->clear(); - } - -private: - std::vector> *calls; -}; + template + connection connect() { + disconnect(); + delegate call{}; + call.template connect(); + signal->calls.insert(signal->calls.end() - offset, std::move(call)); -/** - * @brief Unmanaged signal handler definition. - * - * Unmanaged signal handler. It works directly with naked pointers 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 used later to notify a bunch of listeners. - * * Collecting results from a set of functions like in a voting system. - * - * The default collector does nothing. To properly collect data, define and use - * a class that has a call operator the signature of which is `bool(Param)` and: - * - * * `Param` is a type to which `Ret` can be converted. - * * The return type is true if the handler must stop collecting data, false - * otherwise. - * - * @tparam Ret Return type of a function type. - * @tparam Args Types of arguments of a function type. - * @tparam Collector Type of collector to use, if any. - */ -template -struct sigh: private internal::invoker { - /*! @brief Unsigned integer type. */ - using size_type = typename std::vector>::size_type; - /*! @brief Collector type. */ - using collector_type = Collector; - /*! @brief Sink type. */ - using sink_type = entt::sink; + delegate conn{}; + conn.template connect<&release>(); + return {std::move(conn), signal}; + } /** - * @brief Instance type when it comes to connecting member functions. - * @tparam Class Type of class to which the member function belongs. + * @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. On the other side, the signal handler performs + * checks to avoid multiple connections for the same function.
+ * 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 object that fits the purpose. + * @return A properly initialized connection object. */ - template - using instance_type = Class *; + template + connection connect(Type &&value_or_instance) { + disconnect(value_or_instance); - /** - * @brief Number of listeners connected to the signal. - * @return Number of listeners currently connected. - */ - size_type size() const ENTT_NOEXCEPT { - return calls.size(); + delegate call{}; + call.template connect(value_or_instance); + signal->calls.insert(signal->calls.end() - offset, std::move(call)); + + delegate conn{}; + conn.template connect<&release>(value_or_instance); + return {std::move(conn), signal}; } /** - * @brief Returns false if at least a listener is connected to the signal. - * @return True if the signal has no listeners connected, false otherwise. + * @brief Disconnects a free function or an unbound member from a signal. + * @tparam Candidate Function or member to disconnect from the signal. */ - bool empty() const ENTT_NOEXCEPT { - return calls.empty(); + template + void disconnect() { + auto &calls = signal->calls; + delegate call{}; + call.template connect(); + calls.erase(std::remove(calls.begin(), calls.end(), std::move(call)), calls.end()); } /** - * @brief Returns a sink object for the given signal. - * - * A sink is an opaque object used to connect listeners to signals.
- * The function type for a listener is the one of the signal to which it - * belongs. The order of invocation of the listeners isn't guaranteed. - * - * @return A temporary sink object. + * @brief Disconnects a free function with payload or a bound member from a + * signal. + * @tparam Candidate Function or member to disconnect from the signal. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid object that fits the purpose. */ - sink_type sink() ENTT_NOEXCEPT { - return { &calls }; + template + void disconnect(Type &&value_or_instance) { + auto &calls = signal->calls; + delegate call{}; + call.template connect(value_or_instance); + calls.erase(std::remove(calls.begin(), calls.end(), std::move(call)), calls.end()); } /** - * @brief Triggers a signal. - * - * All the listeners are notified. Order isn't guaranteed. - * - * @param args Arguments to use to invoke listeners. + * @brief Disconnects free functions with payload or bound members from a + * signal. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid object that fits the purpose. */ - void publish(Args... args) const { - for(auto pos = calls.size(); pos; --pos) { - auto &call = calls[pos-1]; - call(args...); - } + template + void disconnect(Type &value_or_instance) { + disconnect(&value_or_instance); } /** - * @brief Collects return values from the listeners. - * @param args Arguments to use to invoke listeners. - * @return An instance of the collector filled with collected data. + * @brief Disconnects free functions with payload or bound members from a + * signal. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid object that fits the purpose. */ - collector_type collect(Args... args) const { - collector_type collector; - - for(auto &&call: calls) { - if(!this->invoke(collector, call, args...)) { - break; - } + template + void disconnect(Type *value_or_instance) { + if(value_or_instance) { + auto &calls = signal->calls; + auto predicate = [value_or_instance](const auto &delegate) { return delegate.data() == value_or_instance; }; + calls.erase(std::remove_if(calls.begin(), calls.end(), std::move(predicate)), calls.end()); } - - return collector; } - /** - * @brief Swaps listeners between the two signals. - * @param lhs A valid signal object. - * @param rhs A valid signal object. - */ - friend void swap(sigh &lhs, sigh &rhs) { - using std::swap; - swap(lhs.calls, rhs.calls); + /*! @brief Disconnects all the listeners from a signal. */ + void disconnect() { + signal->calls.clear(); } private: - std::vector> calls; + difference_type offset; + 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 // ENTT_SIGNAL_SIGH_HPP +#endif diff --git a/modules/entt/test/CMakeLists.txt b/modules/entt/test/CMakeLists.txt index 681d109..a99436f 100644 --- a/modules/entt/test/CMakeLists.txt +++ b/modules/entt/test/CMakeLists.txt @@ -2,128 +2,261 @@ # Tests configuration # +include(FetchContent) +include(CheckCXXSourceCompiles) + +check_cxx_source_compiles(" + #include + int main() { return 0; } +" ENTT_HAS_HEADER_VERSION) + +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + +if(ENTT_FIND_GTEST_PACKAGE) + find_package(GTest REQUIRED) +else() + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG main + GIT_SHALLOW 1 + ) + + FetchContent_GetProperties(googletest) + + if(NOT googletest_POPULATED) + FetchContent_Populate(googletest) + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) + endif() + + add_library(GTest::Main ALIAS gtest_main) + + target_compile_features(gtest PUBLIC cxx_std_17) + target_compile_features(gtest_main PUBLIC cxx_std_17) + target_compile_features(gmock PUBLIC cxx_std_17) + target_compile_features(gmock_main PUBLIC cxx_std_17) +endif() + include_directories($) add_compile_options($) -macro(SETUP_LIBRARY_TARGET LIB_TARGET) - set_target_properties(${LIB_TARGET} PROPERTIES CXX_EXTENSIONS OFF) - target_compile_definitions(${LIB_TARGET} PRIVATE $) - target_compile_features(${LIB_TARGET} PRIVATE $) - target_compile_options(${LIB_TARGET} PRIVATE $<$>:-pedantic -Wall>) - target_compile_options(${LIB_TARGET} PRIVATE $<$:/EHsc>) -endmacro() +function(SETUP_TARGET TARGET_NAME) + set_target_properties(${TARGET_NAME} PROPERTIES CXX_EXTENSIONS OFF) + target_compile_features(${TARGET_NAME} PRIVATE ${ENTT_CXX_STD}) + target_link_libraries(${TARGET_NAME} PRIVATE EnTT) + + if(MSVC) + target_compile_options( + ${TARGET_NAME} + PRIVATE + /EHsc /W1 /wd4996 /w14800 + $<$:/Od> + $<$:/O2> + ) + else() + target_compile_options( + ${TARGET_NAME} + PRIVATE + -pedantic -fvisibility=hidden -Wall -Wshadow -Wno-deprecated-declarations + $<$:-O0 -g> + $<$:-O2> + ) + endif() + + target_compile_definitions( + ${TARGET_NAME} + PRIVATE + ENTT_ID_TYPE=${ENTT_ID_TYPE} + _ENABLE_EXTENDED_ALIGNED_STORAGE + NOMINMAX + ${ARGN} + ) + + if(ENTT_HAS_HEADER_VERSION) + target_compile_definitions( + ${TARGET_NAME} + PRIVATE + ENTT_HAS_HEADER_VERSION + ) + endif() +endfunction() add_library(odr OBJECT odr.cpp) -SETUP_LIBRARY_TARGET(odr) - -macro(SETUP_AND_ADD_TEST TEST_NAME TEST_SOURCE) - add_executable(${TEST_NAME} $ ${TEST_SOURCE}) - set_target_properties(${TEST_NAME} PROPERTIES CXX_EXTENSIONS OFF) - target_link_libraries(${TEST_NAME} PRIVATE EnTT GTest::Main Threads::Threads) - target_compile_definitions(${TEST_NAME} PRIVATE $) - target_compile_features(${TEST_NAME} PRIVATE $) - target_compile_options(${TEST_NAME} PRIVATE $<$>:-pedantic -Wall>) - target_compile_options(${TEST_NAME} PRIVATE $<$:/EHsc>) +SETUP_TARGET(odr) + +function(SETUP_BASIC_TEST TEST_NAME TEST_SOURCES) + add_executable(${TEST_NAME} $ ${TEST_SOURCES}) + target_link_libraries(${TEST_NAME} PRIVATE GTest::Main Threads::Threads) + SETUP_TARGET(${TEST_NAME} ${ARGN}) add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) -endmacro() +endfunction() + +function(SETUP_LIB_TEST TEST_NAME) + add_library(_${TEST_NAME} SHARED $ lib/${TEST_NAME}/lib.cpp) + SETUP_TARGET(_${TEST_NAME} ENTT_API_EXPORT) + SETUP_BASIC_TEST(lib_${TEST_NAME} lib/${TEST_NAME}/main.cpp ENTT_API_IMPORT) + target_link_libraries(lib_${TEST_NAME} PRIVATE _${TEST_NAME}) +endfunction() + +function(SETUP_PLUGIN_TEST TEST_NAME) + add_library(_${TEST_NAME} MODULE $ lib/${TEST_NAME}/plugin.cpp) + SETUP_TARGET(_${TEST_NAME} ${ARGVN}) + SETUP_BASIC_TEST(lib_${TEST_NAME} lib/${TEST_NAME}/main.cpp PLUGIN="$" ${ARGVN}) + target_include_directories(_${TEST_NAME} PRIVATE ${cr_INCLUDE_DIR}) + target_include_directories(lib_${TEST_NAME} PRIVATE ${cr_INCLUDE_DIR}) + target_link_libraries(lib_${TEST_NAME} PRIVATE ${CMAKE_DL_LIBS}) +endfunction() # Test benchmark -if(BUILD_BENCHMARK) - SETUP_AND_ADD_TEST(benchmark benchmark/benchmark.cpp) +if(ENTT_BUILD_BENCHMARK) + SETUP_BASIC_TEST(benchmark benchmark/benchmark.cpp) endif() -# Test lib +# Test example -if(BUILD_LIB) - add_library(a_module_shared SHARED lib/a_module.cpp) - add_library(a_module_static STATIC lib/a_module.cpp) - add_library(another_module_shared SHARED lib/another_module.cpp) - add_library(another_module_static STATIC lib/another_module.cpp) +if(ENTT_BUILD_EXAMPLE) + SETUP_BASIC_TEST(custom_identifier example/custom_identifier.cpp) + SETUP_BASIC_TEST(entity_copy example/entity_copy.cpp) + SETUP_BASIC_TEST(signal_less example/signal_less.cpp) +endif() - SETUP_LIBRARY_TARGET(a_module_shared) - SETUP_LIBRARY_TARGET(a_module_static) - SETUP_LIBRARY_TARGET(another_module_shared) - SETUP_LIBRARY_TARGET(another_module_static) +# Test lib - SETUP_AND_ADD_TEST(lib_shared lib/lib.cpp) - target_link_libraries(lib_shared PRIVATE a_module_shared another_module_shared) +if(ENTT_BUILD_LIB) + FetchContent_Declare( + cr + GIT_REPOSITORY https://github.com/fungos/cr.git + GIT_TAG master + GIT_SHALLOW 1 + ) - SETUP_AND_ADD_TEST(lib_static lib/lib.cpp) - target_link_libraries(lib_static PRIVATE a_module_static another_module_static) -endif() + FetchContent_GetProperties(cr) + + if(NOT cr_POPULATED) + FetchContent_Populate(cr) + set(cr_INCLUDE_DIR ${cr_SOURCE_DIR}) + endif() -# Test mod + SETUP_LIB_TEST(dispatcher) + SETUP_LIB_TEST(emitter) + SETUP_LIB_TEST(meta) + SETUP_LIB_TEST(registry) -if(BUILD_MOD) - set(DUKTAPE_DEPS_DIR ${EnTT_SOURCE_DIR}/deps/duktape) - configure_file(${EnTT_SOURCE_DIR}/cmake/in/duktape.in ${DUKTAPE_DEPS_DIR}/CMakeLists.txt) - execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${DUKTAPE_DEPS_DIR}) - execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${DUKTAPE_DEPS_DIR}) - set(DUKTAPE_SRC_DIR ${DUKTAPE_DEPS_DIR}/src/src) + SETUP_PLUGIN_TEST(dispatcher_plugin) + SETUP_PLUGIN_TEST(emitter_plugin) + SETUP_PLUGIN_TEST(meta_plugin) + SETUP_PLUGIN_TEST(registry_plugin) - set(MOD_TEST_SOURCE ${DUKTAPE_SRC_DIR}/duktape.c mod/mod.cpp) - SETUP_AND_ADD_TEST(mod "${MOD_TEST_SOURCE}") - target_include_directories(mod PRIVATE ${DUKTAPE_SRC_DIR}) + SETUP_PLUGIN_TEST(meta_plugin_std ENTT_STANDARD_CPP) endif() # Test snapshot -if(BUILD_SNAPSHOT) - set(CEREAL_DEPS_DIR ${EnTT_SOURCE_DIR}/deps/cereal) - configure_file(${EnTT_SOURCE_DIR}/cmake/in/cereal.in ${CEREAL_DEPS_DIR}/CMakeLists.txt) - execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY ${CEREAL_DEPS_DIR}) - execute_process(COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${CEREAL_DEPS_DIR}) - set(CEREAL_SRC_DIR ${CEREAL_DEPS_DIR}/src/include) +if(ENTT_BUILD_SNAPSHOT) + FetchContent_Declare( + cereal + GIT_REPOSITORY https://github.com/USCiLab/cereal.git + GIT_TAG v1.2.2 + GIT_SHALLOW 1 + ) + + FetchContent_GetProperties(cereal) - SETUP_AND_ADD_TEST(cereal snapshot/snapshot.cpp) - target_include_directories(cereal PRIVATE ${CEREAL_SRC_DIR}) + if(NOT cereal_POPULATED) + FetchContent_Populate(cereal) + set(cereal_INCLUDE_DIR ${cereal_SOURCE_DIR}/include) + endif() + + SETUP_BASIC_TEST(cereal snapshot/snapshot.cpp) + target_include_directories(cereal PRIVATE ${cereal_INCLUDE_DIR}) endif() +# Test config + +SETUP_BASIC_TEST(version entt/config/version.cpp) + +# Test container + +SETUP_BASIC_TEST(dense_map entt/container/dense_map.cpp) +SETUP_BASIC_TEST(dense_set entt/container/dense_set.cpp) + # Test core -SETUP_AND_ADD_TEST(algorithm entt/core/algorithm.cpp) -SETUP_AND_ADD_TEST(family entt/core/family.cpp) -SETUP_AND_ADD_TEST(hashed_string entt/core/hashed_string.cpp) -SETUP_AND_ADD_TEST(ident entt/core/ident.cpp) -SETUP_AND_ADD_TEST(monostate entt/core/monostate.cpp) -SETUP_AND_ADD_TEST(type_traits entt/core/type_traits.cpp) -SETUP_AND_ADD_TEST(utility entt/core/utility.cpp) +SETUP_BASIC_TEST(algorithm entt/core/algorithm.cpp) +SETUP_BASIC_TEST(any entt/core/any.cpp) +SETUP_BASIC_TEST(compressed_pair entt/core/compressed_pair.cpp) +SETUP_BASIC_TEST(enum entt/core/enum.cpp) +SETUP_BASIC_TEST(family entt/core/family.cpp) +SETUP_BASIC_TEST(hashed_string entt/core/hashed_string.cpp) +SETUP_BASIC_TEST(ident entt/core/ident.cpp) +SETUP_BASIC_TEST(iterator entt/core/iterator.cpp) +SETUP_BASIC_TEST(memory entt/core/memory.cpp) +SETUP_BASIC_TEST(monostate entt/core/monostate.cpp) +SETUP_BASIC_TEST(tuple entt/core/tuple.cpp) +SETUP_BASIC_TEST(type_info entt/core/type_info.cpp) +SETUP_BASIC_TEST(type_traits entt/core/type_traits.cpp) +SETUP_BASIC_TEST(utility entt/core/utility.cpp) # Test entity -SETUP_AND_ADD_TEST(actor entt/entity/actor.cpp) -SETUP_AND_ADD_TEST(entity entt/entity/entity.cpp) -SETUP_AND_ADD_TEST(group entt/entity/group.cpp) -SETUP_AND_ADD_TEST(helper entt/entity/helper.cpp) -SETUP_AND_ADD_TEST(prototype entt/entity/prototype.cpp) -SETUP_AND_ADD_TEST(registry entt/entity/registry.cpp) -SETUP_AND_ADD_TEST(runtime_view entt/entity/runtime_view.cpp) -SETUP_AND_ADD_TEST(snapshot entt/entity/snapshot.cpp) -SETUP_AND_ADD_TEST(sparse_set entt/entity/sparse_set.cpp) -SETUP_AND_ADD_TEST(storage entt/entity/storage.cpp) -SETUP_AND_ADD_TEST(view entt/entity/view.cpp) +SETUP_BASIC_TEST(component entt/entity/component.cpp) +SETUP_BASIC_TEST(entity entt/entity/entity.cpp) +SETUP_BASIC_TEST(group entt/entity/group.cpp) +SETUP_BASIC_TEST(handle entt/entity/handle.cpp) +SETUP_BASIC_TEST(helper entt/entity/helper.cpp) +SETUP_BASIC_TEST(observer entt/entity/observer.cpp) +SETUP_BASIC_TEST(organizer entt/entity/organizer.cpp) +SETUP_BASIC_TEST(registry entt/entity/registry.cpp) +SETUP_BASIC_TEST(runtime_view entt/entity/runtime_view.cpp) +SETUP_BASIC_TEST(sigh_storage_mixin entt/entity/sigh_storage_mixin.cpp) +SETUP_BASIC_TEST(snapshot entt/entity/snapshot.cpp) +SETUP_BASIC_TEST(sparse_set entt/entity/sparse_set.cpp) +SETUP_BASIC_TEST(storage entt/entity/storage.cpp) +SETUP_BASIC_TEST(view entt/entity/view.cpp) # Test locator -SETUP_AND_ADD_TEST(locator entt/locator/locator.cpp) +SETUP_BASIC_TEST(locator entt/locator/locator.cpp) # Test meta -SETUP_AND_ADD_TEST(meta entt/meta/meta.cpp) +SETUP_BASIC_TEST(meta_any entt/meta/meta_any.cpp) +SETUP_BASIC_TEST(meta_base entt/meta/meta_base.cpp) +SETUP_BASIC_TEST(meta_container entt/meta/meta_container.cpp) +SETUP_BASIC_TEST(meta_conv entt/meta/meta_conv.cpp) +SETUP_BASIC_TEST(meta_ctor entt/meta/meta_ctor.cpp) +SETUP_BASIC_TEST(meta_data entt/meta/meta_data.cpp) +SETUP_BASIC_TEST(meta_dtor entt/meta/meta_dtor.cpp) +SETUP_BASIC_TEST(meta_func entt/meta/meta_func.cpp) +SETUP_BASIC_TEST(meta_handle entt/meta/meta_handle.cpp) +SETUP_BASIC_TEST(meta_pointer entt/meta/meta_pointer.cpp) +SETUP_BASIC_TEST(meta_prop entt/meta/meta_prop.cpp) +SETUP_BASIC_TEST(meta_range entt/meta/meta_range.cpp) +SETUP_BASIC_TEST(meta_template entt/meta/meta_template.cpp) +SETUP_BASIC_TEST(meta_type entt/meta/meta_type.cpp) +SETUP_BASIC_TEST(meta_utility entt/meta/meta_utility.cpp) + +# Test poly + +SETUP_BASIC_TEST(poly entt/poly/poly.cpp) # Test process -SETUP_AND_ADD_TEST(process entt/process/process.cpp) -SETUP_AND_ADD_TEST(scheduler entt/process/scheduler.cpp) +SETUP_BASIC_TEST(process entt/process/process.cpp) +SETUP_BASIC_TEST(scheduler entt/process/scheduler.cpp) # Test resource -SETUP_AND_ADD_TEST(resource entt/resource/resource.cpp) +SETUP_BASIC_TEST(resource entt/resource/resource.cpp) +SETUP_BASIC_TEST(resource_cache entt/resource/resource_cache.cpp) +SETUP_BASIC_TEST(resource_loader entt/resource/resource_loader.cpp) # Test signal -SETUP_AND_ADD_TEST(delegate entt/signal/delegate.cpp) -SETUP_AND_ADD_TEST(dispatcher entt/signal/dispatcher.cpp) -SETUP_AND_ADD_TEST(emitter entt/signal/emitter.cpp) -SETUP_AND_ADD_TEST(sigh entt/signal/sigh.cpp) +SETUP_BASIC_TEST(delegate entt/signal/delegate.cpp) +SETUP_BASIC_TEST(dispatcher entt/signal/dispatcher.cpp) +SETUP_BASIC_TEST(emitter entt/signal/emitter.cpp) +SETUP_BASIC_TEST(sigh entt/signal/sigh.cpp) diff --git a/modules/entt/test/benchmark/benchmark.cpp b/modules/entt/test/benchmark/benchmark.cpp index 5d93c88..ec7c608 100644 --- a/modules/entt/test/benchmark/benchmark.cpp +++ b/modules/entt/test/benchmark/benchmark.cpp @@ -1,8 +1,8 @@ -#include -#include -#include #include -#include +#include +#include +#include +#include #include #include @@ -11,16 +11,18 @@ struct position { std::uint64_t y; }; -struct velocity { - std::uint64_t x; - std::uint64_t y; +struct velocity: position {}; + +struct stable_position: position { + static constexpr auto in_place_delete = true; }; -template +template struct comp { int x; }; struct timer final { - timer(): start{std::chrono::system_clock::now()} {} + timer() + : start{std::chrono::system_clock::now()} {} void elapsed() { auto now = std::chrono::system_clock::now(); @@ -31,107 +33,265 @@ private: std::chrono::time_point start; }; +template +void generic(Iterable &&iterable, Func func) { + timer timer; + std::forward(iterable).each(func); + timer.elapsed(); +} + template void pathological(Func func) { entt::registry registry; for(std::uint64_t i = 0; i < 500000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace(entity); + registry.emplace>(entity); } for(auto i = 0; i < 10; ++i) { registry.each([i = 0, ®istry](const auto entity) mutable { - if(!(++i % 7)) { registry.reset(entity); } - if(!(++i % 11)) { registry.reset(entity); } - if(!(++i % 13)) { registry.reset>(entity); } + if(!(++i % 7)) { registry.remove(entity); } + if(!(++i % 11)) { registry.remove(entity); } + if(!(++i % 13)) { registry.remove>(entity); } if(!(++i % 17)) { registry.destroy(entity); } }); for(std::uint64_t j = 0; j < 50000L; j++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace(entity); + registry.emplace>(entity); } } - func(registry, [](auto &... comp) { - ((comp.x = {}), ...); - }); + timer timer; + func(registry).each([](auto &...comp) { ((comp.x = {}), ...); }); + timer.elapsed(); } -TEST(Benchmark, Construct) { +TEST(Benchmark, Create) { entt::registry registry; - std::cout << "Constructing 1000000 entities" << std::endl; + std::cout << "Creating 1000000 entities" << std::endl; timer timer; for(std::uint64_t i = 0; i < 1000000L; i++) { - registry.create(); + static_cast(registry.create()); } timer.elapsed(); } -TEST(Benchmark, ConstructMany) { +TEST(Benchmark, CreateMany) { entt::registry registry; std::vector entities(1000000); - std::cout << "Constructing 1000000 entities at once" << std::endl; + std::cout << "Creating 1000000 entities at once" << std::endl; timer timer; registry.create(entities.begin(), entities.end()); timer.elapsed(); } -TEST(Benchmark, ConstructManyAndAssignComponents) { +TEST(Benchmark, CreateManyAndEmplaceComponents) { entt::registry registry; std::vector entities(1000000); - std::cout << "Constructing 1000000 entities at once and assign components" << std::endl; + std::cout << "Creating 1000000 entities at once and emplace components" << std::endl; timer timer; - registry.create(entities.begin(), entities.end()); for(const auto entity: entities) { - registry.assign(entity); - registry.assign(entity); + registry.emplace(entity); + registry.emplace(entity); } timer.elapsed(); } -TEST(Benchmark, ConstructManyWithComponents) { +TEST(Benchmark, CreateManyWithComponents) { entt::registry registry; std::vector entities(1000000); - std::cout << "Constructing 1000000 entities at once with components" << std::endl; + std::cout << "Creating 1000000 entities at once with components" << std::endl; timer timer; - registry.create(entities.begin(), entities.end()); + registry.create(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); timer.elapsed(); } -TEST(Benchmark, Destroy) { +TEST(Benchmark, Erase) { entt::registry registry; + std::vector entities(1000000); + auto view = registry.view(); - std::cout << "Destroying 1000000 entities" << std::endl; + std::cout << "Erasing 1000000 components from their entities" << std::endl; - for(std::uint64_t i = 0; i < 1000000L; i++) { - registry.create(); + registry.create(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + + timer timer; + + for(auto entity: view) { + registry.erase(entity); } + timer.elapsed(); +} + +TEST(Benchmark, EraseMany) { + entt::registry registry; + std::vector entities(1000000); + auto view = registry.view(); + + std::cout << "Erasing 1000000 components from their entities at once" << std::endl; + + registry.create(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + timer timer; + registry.erase(view.begin(), view.end()); + timer.elapsed(); +} + +TEST(Benchmark, Remove) { + entt::registry registry; + std::vector entities(1000000); + auto view = registry.view(); + + std::cout << "Removing 1000000 components from their entities" << std::endl; + + registry.create(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + + timer timer; + + for(auto entity: view) { + registry.remove(entity); + } + + timer.elapsed(); +} + +TEST(Benchmark, RemoveMany) { + entt::registry registry; + std::vector entities(1000000); + auto view = registry.view(); + + std::cout << "Removing 1000000 components from their entities at once" << std::endl; + + registry.create(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + + timer timer; + registry.remove(view.begin(), view.end()); + timer.elapsed(); +} + +TEST(Benchmark, Clear) { + entt::registry registry; + std::vector entities(1000000); + + std::cout << "Clearing 1000000 components from their entities" << std::endl; + + registry.create(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + + timer timer; + registry.clear(); + timer.elapsed(); +} + +TEST(Benchmark, Recycle) { + entt::registry registry; + std::vector entities(1000000); + + std::cout << "Recycling 1000000 entities" << std::endl; + + registry.create(entities.begin(), entities.end()); + + registry.each([®istry](auto entity) { + registry.destroy(entity); + }); + + timer timer; + + for(auto next = entities.size(); next; --next) { + static_cast(registry.create()); + } + + timer.elapsed(); +} + +TEST(Benchmark, RecycleMany) { + entt::registry registry; + std::vector entities(1000000); + + std::cout << "Recycling 1000000 entities" << std::endl; + + registry.create(entities.begin(), entities.end()); registry.each([®istry](auto entity) { registry.destroy(entity); }); + timer timer; + registry.create(entities.begin(), entities.end()); + timer.elapsed(); +} + +TEST(Benchmark, Destroy) { + entt::registry registry; + std::vector entities(1000000); + auto view = registry.view(); + + std::cout << "Destroying 1000000 entities" << std::endl; + + registry.create(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + + timer timer; + + for(auto entity: view) { + registry.destroy(entity); + } + + timer.elapsed(); +} + +TEST(Benchmark, DestroyMany) { + entt::registry registry; + std::vector entities(1000000); + auto view = registry.view(); + + std::cout << "Destroying 1000000 entities at once" << std::endl; + + registry.create(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + + timer timer; + registry.destroy(view.begin(), view.end()); + timer.elapsed(); +} + +TEST(Benchmark, DestroyManyFastPath) { + entt::registry registry; + std::vector entities(1000000); + + std::cout << "Destroying 1000000 entities at once, fast path" << std::endl; + + registry.create(entities.begin(), entities.end()); + registry.insert(entities.begin(), entities.end()); + + timer timer; + registry.destroy(entities.begin(), entities.end()); timer.elapsed(); } @@ -142,16 +302,25 @@ TEST(Benchmark, IterateSingleComponent1M) { for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); + registry.emplace(entity); } - auto test = [®istry](auto func) { - timer timer; - registry.view().each(func); - timer.elapsed(); - }; + generic(registry.view(), [](auto &...comp) { + ((comp.x = {}), ...); + }); +} - test([](auto &... comp) { +TEST(Benchmark, IterateSingleComponentTombstonePolicy1M) { + entt::registry registry; + + std::cout << "Iterating over 1000000 entities, one component, tombstone policy" << std::endl; + + for(std::uint64_t i = 0; i < 1000000L; i++) { + const auto entity = registry.create(); + registry.emplace(entity); + } + + generic(registry.view(), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -163,19 +332,13 @@ TEST(Benchmark, IterateSingleComponentRuntime1M) { for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); + registry.emplace(entity); } - auto test = [®istry](auto func) { - using component_type = typename entt::registry::component_type; - component_type types[] = { registry.type() }; - - timer timer; - registry.runtime_view(std::begin(types), std::end(types)).each(func); - timer.elapsed(); - }; + entt::runtime_view view{}; + view.iterate(registry.storage()); - test([®istry](auto entity) { + generic(view, [®istry](auto entity) { registry.get(entity).x = {}; }); } @@ -187,17 +350,27 @@ TEST(Benchmark, IterateTwoComponents1M) { for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); + registry.emplace(entity); + registry.emplace(entity); } - auto test = [®istry](auto func) { - timer timer; - registry.view().each(func); - timer.elapsed(); - }; + generic(registry.view(), [](auto &...comp) { + ((comp.x = {}), ...); + }); +} + +TEST(Benchmark, IterateTombstonePolicyTwoComponentsTombstonePolicy1M) { + entt::registry registry; + + std::cout << "Iterating over 1000000 entities, two components, tombstone policy" << std::endl; + + for(std::uint64_t i = 0; i < 1000000L; i++) { + const auto entity = registry.create(); + registry.emplace(entity); + registry.emplace(entity); + } - test([](auto &... comp) { + generic(registry.view(), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -209,20 +382,14 @@ TEST(Benchmark, IterateTwoComponents1MHalf) { for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); + registry.emplace(entity); if(i % 2) { - registry.assign(entity); + registry.emplace(entity); } } - auto test = [®istry](auto func) { - timer timer; - registry.view().each(func); - timer.elapsed(); - }; - - test([](auto &... comp) { + generic(registry.view(), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -234,89 +401,62 @@ TEST(Benchmark, IterateTwoComponents1MOne) { for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); + registry.emplace(entity); if(i == 500000L) { - registry.assign(entity); + registry.emplace(entity); } } - auto test = [®istry](auto func) { - timer timer; - registry.view().each(func); - timer.elapsed(); - }; - - test([](auto &... comp) { + generic(registry.view(), [](auto &...comp) { ((comp.x = {}), ...); }); } TEST(Benchmark, IterateTwoComponentsNonOwningGroup1M) { entt::registry registry; - registry.group<>(entt::get); std::cout << "Iterating over 1000000 entities, two components, non owning group" << std::endl; for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); + registry.emplace(entity); + registry.emplace(entity); } - auto test = [®istry](auto func) { - timer timer; - registry.group<>(entt::get).each(func); - timer.elapsed(); - }; - - test([](auto &... comp) { + generic(registry.group<>(entt::get), [](auto &...comp) { ((comp.x = {}), ...); }); } TEST(Benchmark, IterateTwoComponentsFullOwningGroup1M) { entt::registry registry; - registry.group(); std::cout << "Iterating over 1000000 entities, two components, full owning group" << std::endl; for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); + registry.emplace(entity); + registry.emplace(entity); } - auto test = [®istry](auto func) { - timer timer; - registry.group().each(func); - timer.elapsed(); - }; - - test([](auto &... comp) { + generic(registry.group(), [](auto &...comp) { ((comp.x = {}), ...); }); } TEST(Benchmark, IterateTwoComponentsPartialOwningGroup1M) { entt::registry registry; - registry.group(entt::get); std::cout << "Iterating over 1000000 entities, two components, partial owning group" << std::endl; for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); + registry.emplace(entity); + registry.emplace(entity); } - auto test = [®istry](auto func) { - timer timer; - registry.group(entt::get).each(func); - timer.elapsed(); - }; - - test([](auto &... comp) { + generic(registry.group(entt::get), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -328,20 +468,15 @@ TEST(Benchmark, IterateTwoComponentsRuntime1M) { for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); + registry.emplace(entity); + registry.emplace(entity); } - auto test = [®istry](auto func) { - using component_type = typename entt::registry::component_type; - component_type types[] = { registry.type(), registry.type() }; - - timer timer; - registry.runtime_view(std::begin(types), std::end(types)).each(func); - timer.elapsed(); - }; + entt::runtime_view view{}; + view.iterate(registry.storage()) + .iterate(registry.storage()); - test([®istry](auto entity) { + generic(view, [®istry](auto entity) { registry.get(entity).x = {}; registry.get(entity).x = {}; }); @@ -354,23 +489,18 @@ TEST(Benchmark, IterateTwoComponentsRuntime1MHalf) { for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); + registry.emplace(entity); if(i % 2) { - registry.assign(entity); + registry.emplace(entity); } } - auto test = [®istry](auto func) { - using component_type = typename entt::registry::component_type; - component_type types[] = { registry.type(), registry.type() }; - - timer timer; - registry.runtime_view(std::begin(types), std::end(types)).each(func); - timer.elapsed(); - }; + entt::runtime_view view{}; + view.iterate(registry.storage()) + .iterate(registry.storage()); - test([®istry](auto entity) { + generic(view, [®istry](auto entity) { registry.get(entity).x = {}; registry.get(entity).x = {}; }); @@ -383,23 +513,18 @@ TEST(Benchmark, IterateTwoComponentsRuntime1MOne) { for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); + registry.emplace(entity); if(i == 500000L) { - registry.assign(entity); + registry.emplace(entity); } } - auto test = [®istry](auto func) { - using component_type = typename entt::registry::component_type; - component_type types[] = { registry.type(), registry.type() }; + entt::runtime_view view{}; + view.iterate(registry.storage()) + .iterate(registry.storage()); - timer timer; - registry.runtime_view(std::begin(types), std::end(types)).each(func); - timer.elapsed(); - }; - - test([®istry](auto entity) { + generic(view, [®istry](auto entity) { registry.get(entity).x = {}; registry.get(entity).x = {}; }); @@ -412,18 +537,29 @@ TEST(Benchmark, IterateThreeComponents1M) { for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace(entity); + registry.emplace>(entity); } - auto test = [®istry](auto func) { - timer timer; - registry.view>().each(func); - timer.elapsed(); - }; + generic(registry.view>(), [](auto &...comp) { + ((comp.x = {}), ...); + }); +} + +TEST(Benchmark, IterateThreeComponentsTombstonePolicy1M) { + entt::registry registry; - test([](auto &... comp) { + std::cout << "Iterating over 1000000 entities, three components, tombstone policy" << std::endl; + + for(std::uint64_t i = 0; i < 1000000L; i++) { + const auto entity = registry.create(); + registry.emplace(entity); + registry.emplace(entity); + registry.emplace>(entity); + } + + generic(registry.view>(), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -435,21 +571,15 @@ TEST(Benchmark, IterateThreeComponents1MHalf) { for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace>(entity); if(i % 2) { - registry.assign(entity); + registry.emplace(entity); } } - auto test = [®istry](auto func) { - timer timer; - registry.view>().each(func); - timer.elapsed(); - }; - - test([](auto &... comp) { + generic(registry.view>(), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -461,93 +591,66 @@ TEST(Benchmark, IterateThreeComponents1MOne) { for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace>(entity); if(i == 500000L) { - registry.assign(entity); + registry.emplace(entity); } } - auto test = [®istry](auto func) { - timer timer; - registry.view>().each(func); - timer.elapsed(); - }; - - test([](auto &... comp) { + generic(registry.view>(), [](auto &...comp) { ((comp.x = {}), ...); }); } TEST(Benchmark, IterateThreeComponentsNonOwningGroup1M) { entt::registry registry; - registry.group<>(entt::get>); std::cout << "Iterating over 1000000 entities, three components, non owning group" << std::endl; for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace(entity); + registry.emplace>(entity); } - auto test = [®istry](auto func) { - timer timer; - registry.group<>(entt::get>).each(func); - timer.elapsed(); - }; - - test([](auto &... comp) { + generic(registry.group<>(entt::get>), [](auto &...comp) { ((comp.x = {}), ...); }); } TEST(Benchmark, IterateThreeComponentsFullOwningGroup1M) { entt::registry registry; - registry.group>(); std::cout << "Iterating over 1000000 entities, three components, full owning group" << std::endl; for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace(entity); + registry.emplace>(entity); } - auto test = [®istry](auto func) { - timer timer; - registry.group>().each(func); - timer.elapsed(); - }; - - test([](auto &... comp) { + generic(registry.group>(), [](auto &...comp) { ((comp.x = {}), ...); }); } TEST(Benchmark, IterateThreeComponentsPartialOwningGroup1M) { entt::registry registry; - registry.group(entt::get>); std::cout << "Iterating over 1000000 entities, three components, partial owning group" << std::endl; for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace(entity); + registry.emplace>(entity); } - auto test = [®istry](auto func) { - timer timer; - registry.group(entt::get>).each(func); - timer.elapsed(); - }; - - test([](auto &... comp) { + generic(registry.group(entt::get>), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -559,21 +662,17 @@ TEST(Benchmark, IterateThreeComponentsRuntime1M) { for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace(entity); + registry.emplace>(entity); } - auto test = [®istry](auto func) { - using component_type = typename entt::registry::component_type; - component_type types[] = { registry.type(), registry.type(), registry.type>() }; - - timer timer; - registry.runtime_view(std::begin(types), std::end(types)).each(func); - timer.elapsed(); - }; + entt::runtime_view view{}; + view.iterate(registry.storage()) + .iterate(registry.storage()) + .iterate(registry.storage>()); - test([®istry](auto entity) { + generic(view, [®istry](auto entity) { registry.get(entity).x = {}; registry.get(entity).x = {}; registry.get>(entity).x = {}; @@ -587,24 +686,20 @@ TEST(Benchmark, IterateThreeComponentsRuntime1MHalf) { for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace>(entity); if(i % 2) { - registry.assign(entity); + registry.emplace(entity); } } - auto test = [®istry](auto func) { - using component_type = typename entt::registry::component_type; - component_type types[] = { registry.type(), registry.type(), registry.type>() }; + entt::runtime_view view{}; + view.iterate(registry.storage()) + .iterate(registry.storage()) + .iterate(registry.storage>()); - timer timer; - registry.runtime_view(std::begin(types), std::end(types)).each(func); - timer.elapsed(); - }; - - test([®istry](auto entity) { + generic(view, [®istry](auto entity) { registry.get(entity).x = {}; registry.get(entity).x = {}; registry.get>(entity).x = {}; @@ -618,24 +713,20 @@ TEST(Benchmark, IterateThreeComponentsRuntime1MOne) { for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace>(entity); if(i == 500000L) { - registry.assign(entity); + registry.emplace(entity); } } - auto test = [®istry](auto func) { - using component_type = typename entt::registry::component_type; - component_type types[] = { registry.type(), registry.type(), registry.type>() }; - - timer timer; - registry.runtime_view(std::begin(types), std::end(types)).each(func); - timer.elapsed(); - }; + entt::runtime_view view{}; + view.iterate(registry.storage()) + .iterate(registry.storage()) + .iterate(registry.storage>()); - test([®istry](auto entity) { + generic(view, [®istry](auto entity) { registry.get(entity).x = {}; registry.get(entity).x = {}; registry.get>(entity).x = {}; @@ -649,20 +740,33 @@ TEST(Benchmark, IterateFiveComponents1M) { for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); - registry.assign>(entity); - registry.assign>(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace(entity); + registry.emplace>(entity); + registry.emplace>(entity); + registry.emplace>(entity); } - auto test = [®istry](auto func) { - timer timer; - registry.view, comp<1>, comp<2>>().each(func); - timer.elapsed(); - }; + generic(registry.view, comp<1>, comp<2>>(), [](auto &...comp) { + ((comp.x = {}), ...); + }); +} + +TEST(Benchmark, IterateFiveComponentsTombstonePolicy1M) { + entt::registry registry; - test([](auto &... comp) { + std::cout << "Iterating over 1000000 entities, five components, tombstone policy" << std::endl; + + for(std::uint64_t i = 0; i < 1000000L; i++) { + const auto entity = registry.create(); + registry.emplace(entity); + registry.emplace(entity); + registry.emplace>(entity); + registry.emplace>(entity); + registry.emplace>(entity); + } + + generic(registry.view, comp<1>, comp<2>>(), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -674,23 +778,17 @@ TEST(Benchmark, IterateFiveComponents1MHalf) { for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign>(entity); - registry.assign>(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace>(entity); + registry.emplace>(entity); + registry.emplace>(entity); if(i % 2) { - registry.assign(entity); + registry.emplace(entity); } } - auto test = [®istry](auto func) { - timer timer; - registry.view, comp<1>, comp<2>>().each(func); - timer.elapsed(); - }; - - test([](auto &... comp) { + generic(registry.view, comp<1>, comp<2>>(), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -702,127 +800,93 @@ TEST(Benchmark, IterateFiveComponents1MOne) { for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign>(entity); - registry.assign>(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace>(entity); + registry.emplace>(entity); + registry.emplace>(entity); if(i == 500000L) { - registry.assign(entity); + registry.emplace(entity); } } - auto test = [®istry](auto func) { - timer timer; - registry.view, comp<1>, comp<2>>().each(func); - timer.elapsed(); - }; - - test([](auto &... comp) { + generic(registry.view, comp<1>, comp<2>>(), [](auto &...comp) { ((comp.x = {}), ...); }); } TEST(Benchmark, IterateFiveComponentsNonOwningGroup1M) { entt::registry registry; - registry.group<>(entt::get, comp<1>, comp<2>>); std::cout << "Iterating over 1000000 entities, five components, non owning group" << std::endl; for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); - registry.assign>(entity); - registry.assign>(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace(entity); + registry.emplace>(entity); + registry.emplace>(entity); + registry.emplace>(entity); } - auto test = [®istry](auto func) { - timer timer; - registry.group<>(entt::get, comp<1>, comp<2>>).each(func); - timer.elapsed(); - }; - - test([](auto &... comp) { + generic(registry.group<>(entt::get, comp<1>, comp<2>>), [](auto &...comp) { ((comp.x = {}), ...); }); } TEST(Benchmark, IterateFiveComponentsFullOwningGroup1M) { entt::registry registry; - registry.group, comp<1>, comp<2>>(); std::cout << "Iterating over 1000000 entities, five components, full owning group" << std::endl; for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); - registry.assign>(entity); - registry.assign>(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace(entity); + registry.emplace>(entity); + registry.emplace>(entity); + registry.emplace>(entity); } - auto test = [®istry](auto func) { - timer timer; - registry.group, comp<1>, comp<2>>().each(func); - timer.elapsed(); - }; - - test([](auto &... comp) { + generic(registry.group, comp<1>, comp<2>>(), [](auto &...comp) { ((comp.x = {}), ...); }); } TEST(Benchmark, IterateFiveComponentsPartialFourOfFiveOwningGroup1M) { entt::registry registry; - registry.group, comp<1>>(entt::get>); std::cout << "Iterating over 1000000 entities, five components, partial (4 of 5) owning group" << std::endl; for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); - registry.assign>(entity); - registry.assign>(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace(entity); + registry.emplace>(entity); + registry.emplace>(entity); + registry.emplace>(entity); } - auto test = [®istry](auto func) { - timer timer; - registry.group, comp<1>>(entt::get>).each(func); - timer.elapsed(); - }; - - test([](auto &... comp) { + generic(registry.group, comp<1>>(entt::get>), [](auto &...comp) { ((comp.x = {}), ...); }); } TEST(Benchmark, IterateFiveComponentsPartialThreeOfFiveOwningGroup1M) { entt::registry registry; - registry.group>(entt::get, comp<2>>); std::cout << "Iterating over 1000000 entities, five components, partial (3 of 5) owning group" << std::endl; for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); - registry.assign>(entity); - registry.assign>(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace(entity); + registry.emplace>(entity); + registry.emplace>(entity); + registry.emplace>(entity); } - auto test = [®istry](auto func) { - timer timer; - registry.group>(entt::get, comp<2>>).each(func); - timer.elapsed(); - }; - - test([](auto &... comp) { + generic(registry.group>(entt::get, comp<2>>), [](auto &...comp) { ((comp.x = {}), ...); }); } @@ -834,29 +898,21 @@ TEST(Benchmark, IterateFiveComponentsRuntime1M) { for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); - registry.assign>(entity); - registry.assign>(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace(entity); + registry.emplace>(entity); + registry.emplace>(entity); + registry.emplace>(entity); } - auto test = [®istry](auto func) { - using component_type = typename entt::registry::component_type; - component_type types[] = { - registry.type(), - registry.type(), - registry.type>(), - registry.type>(), - registry.type>() - }; - - timer timer; - registry.runtime_view(std::begin(types), std::end(types)).each(func); - timer.elapsed(); - }; - - test([®istry](auto entity) { + entt::runtime_view view{}; + view.iterate(registry.storage()) + .iterate(registry.storage()) + .iterate(registry.storage>()) + .iterate(registry.storage>()) + .iterate(registry.storage>()); + + generic(view, [®istry](auto entity) { registry.get(entity).x = {}; registry.get(entity).x = {}; registry.get>(entity).x = {}; @@ -872,32 +928,24 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MHalf) { for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign>(entity); - registry.assign>(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace>(entity); + registry.emplace>(entity); + registry.emplace>(entity); if(i % 2) { - registry.assign(entity); + registry.emplace(entity); } } - auto test = [®istry](auto func) { - using component_type = typename entt::registry::component_type; - component_type types[] = { - registry.type(), - registry.type(), - registry.type>(), - registry.type>(), - registry.type>() - }; - - timer timer; - registry.runtime_view(std::begin(types), std::end(types)).each(func); - timer.elapsed(); - }; - - test([®istry](auto entity) { + entt::runtime_view view{}; + view.iterate(registry.storage()) + .iterate(registry.storage()) + .iterate(registry.storage>()) + .iterate(registry.storage>()) + .iterate(registry.storage>()); + + generic(view, [®istry](auto entity) { registry.get(entity).x = {}; registry.get(entity).x = {}; registry.get>(entity).x = {}; @@ -913,32 +961,24 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MOne) { for(std::uint64_t i = 0; i < 1000000L; i++) { const auto entity = registry.create(); - registry.assign(entity); - registry.assign>(entity); - registry.assign>(entity); - registry.assign>(entity); + registry.emplace(entity); + registry.emplace>(entity); + registry.emplace>(entity); + registry.emplace>(entity); if(i == 500000L) { - registry.assign(entity); + registry.emplace(entity); } } - auto test = [®istry](auto func) { - using component_type = typename entt::registry::component_type; - component_type types[] = { - registry.type(), - registry.type(), - registry.type>(), - registry.type>(), - registry.type>() - }; - - timer timer; - registry.runtime_view(std::begin(types), std::end(types)).each(func); - timer.elapsed(); - }; - - test([®istry](auto entity) { + entt::runtime_view view{}; + view.iterate(registry.storage()) + .iterate(registry.storage()) + .iterate(registry.storage>()) + .iterate(registry.storage>()) + .iterate(registry.storage>()); + + generic(view, [®istry](auto entity) { registry.get(entity).x = {}; registry.get(entity).x = {}; registry.get>(entity).x = {}; @@ -949,48 +989,22 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MOne) { TEST(Benchmark, IteratePathological) { std::cout << "Pathological case" << std::endl; - - pathological([](auto ®istry, auto func) { - timer timer; - registry.template view>().each(func); - timer.elapsed(); - }); + pathological([](auto ®istry) { return registry.template view>(); }); } TEST(Benchmark, IteratePathologicalNonOwningGroup) { std::cout << "Pathological case (non-owning group)" << std::endl; - - pathological([](auto ®istry, auto func) { - auto group = registry.template group<>(entt::get>); - - timer timer; - group.each(func); - timer.elapsed(); - }); + pathological([](auto ®istry) { return registry.template group<>(entt::get>); }); } TEST(Benchmark, IteratePathologicalFullOwningGroup) { std::cout << "Pathological case (full-owning group)" << std::endl; - - pathological([](auto ®istry, auto func) { - auto group = registry.template group>(); - - timer timer; - group.each(func); - timer.elapsed(); - }); + pathological([](auto ®istry) { return registry.template group>(); }); } TEST(Benchmark, IteratePathologicalPartialOwningGroup) { std::cout << "Pathological case (partial-owning group)" << std::endl; - - pathological([](auto ®istry, auto func) { - auto group = registry.template group(entt::get>); - - timer timer; - group.each(func); - timer.elapsed(); - }); + pathological([](auto ®istry) { return registry.template group(entt::get>); }); } TEST(Benchmark, SortSingle) { @@ -1000,15 +1014,11 @@ TEST(Benchmark, SortSingle) { for(std::uint64_t i = 0; i < 150000L; i++) { const auto entity = registry.create(); - registry.assign(entity, i, i); + registry.emplace(entity, i, i); } timer timer; - - registry.sort([](const auto &lhs, const auto &rhs) { - return lhs.x < rhs.x && lhs.y < rhs.y; - }); - + registry.sort([](const auto &lhs, const auto &rhs) { return lhs.x < rhs.x && lhs.y < rhs.y; }); timer.elapsed(); } @@ -1019,30 +1029,26 @@ TEST(Benchmark, SortMulti) { for(std::uint64_t i = 0; i < 150000L; i++) { const auto entity = registry.create(); - registry.assign(entity, i, i); - registry.assign(entity, i, i); + registry.emplace(entity, i, i); + registry.emplace(entity, i, i); } - registry.sort([](const auto &lhs, const auto &rhs) { - return lhs.x < rhs.x && lhs.y < rhs.y; - }); + registry.sort([](const auto &lhs, const auto &rhs) { return lhs.x < rhs.x && lhs.y < rhs.y; }); timer timer; - registry.sort(); - timer.elapsed(); } TEST(Benchmark, AlmostSortedStdSort) { entt::registry registry; - entt::entity entities[3]; + entt::entity entities[3]{}; std::cout << "Sort 150000 entities, almost sorted, std::sort" << std::endl; for(std::uint64_t i = 0; i < 150000L; i++) { const auto entity = registry.create(); - registry.assign(entity, i, i); + registry.emplace(entity, i, i); if(!(i % 50000)) { entities[i / 50000] = entity; @@ -1052,27 +1058,23 @@ TEST(Benchmark, AlmostSortedStdSort) { for(std::uint64_t i = 0; i < 3; ++i) { registry.destroy(entities[i]); const auto entity = registry.create(); - registry.assign(entity, 50000 * i, 50000 * i); + registry.emplace(entity, 50000 * i, 50000 * i); } timer timer; - - registry.sort([](const auto &lhs, const auto &rhs) { - return lhs.x > rhs.x && lhs.y > rhs.y; - }); - + registry.sort([](const auto &lhs, const auto &rhs) { return lhs.x > rhs.x && lhs.y > rhs.y; }); timer.elapsed(); } TEST(Benchmark, AlmostSortedInsertionSort) { entt::registry registry; - entt::entity entities[3]; + entt::entity entities[3]{}; std::cout << "Sort 150000 entities, almost sorted, insertion sort" << std::endl; for(std::uint64_t i = 0; i < 150000L; i++) { const auto entity = registry.create(); - registry.assign(entity, i, i); + registry.emplace(entity, i, i); if(!(i % 50000)) { entities[i / 50000] = entity; @@ -1082,14 +1084,10 @@ TEST(Benchmark, AlmostSortedInsertionSort) { for(std::uint64_t i = 0; i < 3; ++i) { registry.destroy(entities[i]); const auto entity = registry.create(); - registry.assign(entity, 50000 * i, 50000 * i); + registry.emplace(entity, 50000 * i, 50000 * i); } timer timer; - - registry.sort([](const auto &lhs, const auto &rhs) { - return lhs.x > rhs.x && lhs.y > rhs.y; - }, entt::insertion_sort{}); - + registry.sort([](const auto &lhs, const auto &rhs) { return lhs.x > rhs.x && lhs.y > rhs.y; }, entt::insertion_sort{}); timer.elapsed(); } diff --git a/modules/entt/test/entt/common/basic_test_allocator.hpp b/modules/entt/test/entt/common/basic_test_allocator.hpp new file mode 100644 index 0000000..40be2c8 --- /dev/null +++ b/modules/entt/test/entt/common/basic_test_allocator.hpp @@ -0,0 +1,28 @@ +#ifndef ENTT_COMMON_BASIC_TEST_ALLOCATOR_HPP +#define ENTT_COMMON_BASIC_TEST_ALLOCATOR_HPP + +#include +#include + +namespace test { + +template +struct basic_test_allocator: std::allocator { + // basic pocca/pocma/pocs allocator + + using base = std::allocator; + using propagate_on_container_copy_assignment = std::true_type; + using propagate_on_container_swap = std::true_type; + + using std::allocator::allocator; + + basic_test_allocator &operator=(const basic_test_allocator &other) { + // necessary to avoid call suppression + base::operator=(other); + return *this; + } +}; + +} // namespace test + +#endif diff --git a/modules/entt/test/entt/common/throwing_allocator.hpp b/modules/entt/test/entt/common/throwing_allocator.hpp new file mode 100644 index 0000000..1db2aba --- /dev/null +++ b/modules/entt/test/entt/common/throwing_allocator.hpp @@ -0,0 +1,69 @@ +#ifndef ENTT_COMMON_THROWING_ALLOCATOR_HPP +#define ENTT_COMMON_THROWING_ALLOCATOR_HPP + +#include +#include +#include + +namespace test { + +template +class throwing_allocator: std::allocator { + template + friend class throwing_allocator; + + using base = std::allocator; + struct test_exception {}; + +public: + using value_type = Type; + using pointer = value_type *; + using const_pointer = const value_type *; + using void_pointer = void *; + using const_void_pointer = const void *; + using propagate_on_container_move_assignment = std::true_type; + using propagate_on_container_swap = std::true_type; + using exception_type = test_exception; + + template + struct rebind { + using other = throwing_allocator; + }; + + throwing_allocator() = default; + + template + throwing_allocator(const throwing_allocator &other) + : base{other} {} + + pointer allocate(std::size_t length) { + if(trigger_on_allocate) { + trigger_on_allocate = false; + throw test_exception{}; + } + + trigger_on_allocate = trigger_after_allocate; + trigger_after_allocate = false; + + return base::allocate(length); + } + + void deallocate(pointer mem, std::size_t length) { + base::deallocate(mem, length); + } + + bool operator==(const throwing_allocator &) const { + return true; + } + + bool operator!=(const throwing_allocator &other) const { + return !(*this == other); + } + + static inline bool trigger_on_allocate{}; + static inline bool trigger_after_allocate{}; +}; + +} // namespace test + +#endif diff --git a/modules/entt/test/entt/common/throwing_type.hpp b/modules/entt/test/entt/common/throwing_type.hpp new file mode 100644 index 0000000..5992fa5 --- /dev/null +++ b/modules/entt/test/entt/common/throwing_type.hpp @@ -0,0 +1,46 @@ +#ifndef ENTT_COMMON_THROWING_TYPE_HPP +#define ENTT_COMMON_THROWING_TYPE_HPP + +namespace test { + +class throwing_type { + struct test_exception {}; + +public: + using exception_type = test_exception; + static constexpr auto moved_from_value = -1; + + throwing_type(int value) + : data{value} {} + + throwing_type(const throwing_type &other) + : data{other.data} { + if(data == trigger_on_value) { + data = moved_from_value; + throw exception_type{}; + } + } + + throwing_type &operator=(const throwing_type &other) { + if(other.data == trigger_on_value) { + data = moved_from_value; + throw exception_type{}; + } + + data = other.data; + return *this; + } + + operator int() const { + return data; + } + + static inline int trigger_on_value{}; + +private: + int data{}; +}; + +} // namespace test + +#endif diff --git a/modules/entt/test/entt/common/tracked_memory_resource.hpp b/modules/entt/test/entt/common/tracked_memory_resource.hpp new file mode 100644 index 0000000..20814c2 --- /dev/null +++ b/modules/entt/test/entt/common/tracked_memory_resource.hpp @@ -0,0 +1,65 @@ +#ifndef ENTT_COMMON_TRACKED_MEMORY_RESOURCE_HPP +#define ENTT_COMMON_TRACKED_MEMORY_RESOURCE_HPP + +#ifdef ENTT_HAS_HEADER_VERSION +# include +# +# if defined(__cpp_lib_memory_resource) && __cpp_lib_memory_resource >= 201603L +# define ENTT_HAS_TRACKED_MEMORY_RESOURCE +# +# include +# include +# include +# include + +namespace test { + +class tracked_memory_resource: public std::pmr::memory_resource { + void *do_allocate(std::size_t bytes, std::size_t alignment) override { + ++alloc_counter; + return std::pmr::get_default_resource()->allocate(bytes, alignment); + } + + void do_deallocate(void *value, std::size_t bytes, std::size_t alignment) override { + ++dealloc_counter; + std::pmr::get_default_resource()->deallocate(value, bytes, alignment); + } + + bool do_is_equal(const std::pmr::memory_resource &other) const ENTT_NOEXCEPT override { + return (this == &other); + } + +public: + using string_type = std::pmr::string; + using size_type = std::size_t; + + static constexpr const char *default_value = "a string long enough to force an allocation (hopefully)"; + + tracked_memory_resource() + : alloc_counter{}, + dealloc_counter{} {} + + size_type do_allocate_counter() const ENTT_NOEXCEPT { + return alloc_counter; + } + + size_type do_deallocate_counter() const ENTT_NOEXCEPT { + return dealloc_counter; + } + + void reset() ENTT_NOEXCEPT { + alloc_counter = 0u; + dealloc_counter = 0u; + } + +private: + size_type alloc_counter; + size_type dealloc_counter; +}; + +} // namespace test + +# endif +#endif + +#endif diff --git a/modules/entt/test/entt/config/version.cpp b/modules/entt/test/entt/config/version.cpp new file mode 100644 index 0000000..8656059 --- /dev/null +++ b/modules/entt/test/entt/config/version.cpp @@ -0,0 +1,8 @@ +#include +#include +#include + +TEST(Version, All) { + ASSERT_STREQ(ENTT_VERSION, ENTT_XSTR(ENTT_VERSION_MAJOR) "." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH)); + ASSERT_TRUE(std::regex_match(ENTT_VERSION, std::regex{"^[0-9]+\\.[0-9]+\\.[0-9]+$"})); +} diff --git a/modules/entt/test/entt/container/dense_map.cpp b/modules/entt/test/entt/container/dense_map.cpp new file mode 100644 index 0000000..ad573e5 --- /dev/null +++ b/modules/entt/test/entt/container/dense_map.cpp @@ -0,0 +1,1188 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../common/throwing_allocator.hpp" +#include "../common/tracked_memory_resource.hpp" + +struct transparent_equal_to { + using is_transparent = void; + + template + constexpr std::enable_if_t, bool> + operator()(const Type &lhs, const Other &rhs) const { + return lhs == static_cast(rhs); + } +}; + +TEST(DenseMap, Functionalities) { + entt::dense_map map; + + ASSERT_NO_THROW([[maybe_unused]] auto alloc = map.get_allocator()); + + ASSERT_TRUE(map.empty()); + ASSERT_EQ(map.size(), 0u); + ASSERT_EQ(map.load_factor(), 0.f); + ASSERT_EQ(map.max_load_factor(), .875f); + + map.max_load_factor(.9f); + + ASSERT_EQ(map.max_load_factor(), .9f); + + ASSERT_EQ(map.begin(), map.end()); + ASSERT_EQ(std::as_const(map).begin(), std::as_const(map).end()); + ASSERT_EQ(map.cbegin(), map.cend()); + + ASSERT_NE(map.max_bucket_count(), 0u); + ASSERT_EQ(map.bucket_count(), 8u); + ASSERT_EQ(map.bucket_size(3u), 0u); + + ASSERT_EQ(map.bucket(0), 0u); + ASSERT_EQ(map.bucket(3), 3u); + ASSERT_EQ(map.bucket(8), 0u); + ASSERT_EQ(map.bucket(10), 2u); + + ASSERT_EQ(map.begin(1u), map.end(1u)); + ASSERT_EQ(std::as_const(map).begin(1u), std::as_const(map).end(1u)); + ASSERT_EQ(map.cbegin(1u), map.cend(1u)); + + ASSERT_FALSE(map.contains(42)); + ASSERT_FALSE(map.contains(4.2)); + + ASSERT_EQ(map.find(42), map.end()); + ASSERT_EQ(map.find(4.2), map.end()); + ASSERT_EQ(std::as_const(map).find(42), map.cend()); + ASSERT_EQ(std::as_const(map).find(4.2), map.cend()); + + ASSERT_EQ(map.hash_function()(42), 42); + ASSERT_TRUE(map.key_eq()(42, 42)); + + map.emplace(0u, 0u); + + ASSERT_FALSE(map.empty()); + ASSERT_EQ(map.size(), 1u); + + ASSERT_NE(map.begin(), map.end()); + ASSERT_NE(std::as_const(map).begin(), std::as_const(map).end()); + ASSERT_NE(map.cbegin(), map.cend()); + + ASSERT_TRUE(map.contains(0u)); + ASSERT_EQ(map.bucket(0u), 0u); + + map.clear(); + + ASSERT_TRUE(map.empty()); + ASSERT_EQ(map.size(), 0u); + + ASSERT_EQ(map.begin(), map.end()); + ASSERT_EQ(std::as_const(map).begin(), std::as_const(map).end()); + ASSERT_EQ(map.cbegin(), map.cend()); + + ASSERT_FALSE(map.contains(0u)); +} + +TEST(DenseMap, Constructors) { + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_map map; + + ASSERT_EQ(map.bucket_count(), minimum_bucket_count); + + map = entt::dense_map{std::allocator{}}; + map = entt::dense_map{2u * minimum_bucket_count, std::allocator{}}; + map = entt::dense_map{4u * minimum_bucket_count, std::hash(), std::allocator{}}; + + map.emplace(3u, 42u); + + entt::dense_map temp{map, map.get_allocator()}; + entt::dense_map other{std::move(temp), map.get_allocator()}; + + ASSERT_EQ(map.size(), 1u); + ASSERT_EQ(other.size(), 1u); + ASSERT_EQ(map.bucket_count(), 4u * minimum_bucket_count); + ASSERT_EQ(other.bucket_count(), 4u * minimum_bucket_count); +} + +TEST(DenseMap, Copy) { + entt::dense_map map; + map.max_load_factor(map.max_load_factor() - .05f); + map.emplace(3u, 42u); + + entt::dense_map other{map}; + + ASSERT_TRUE(map.contains(3u)); + ASSERT_TRUE(other.contains(3u)); + ASSERT_EQ(map.max_load_factor(), other.max_load_factor()); + + map.emplace(1u, 99u); + map.emplace(11u, 77u); + other.emplace(0u, 0u); + other = map; + + ASSERT_TRUE(other.contains(3u)); + ASSERT_TRUE(other.contains(1u)); + ASSERT_TRUE(other.contains(11u)); + ASSERT_FALSE(other.contains(0u)); + + ASSERT_EQ(other[3u], 42u); + ASSERT_EQ(other[1u], 99u); + ASSERT_EQ(other[11u], 77u); + + ASSERT_EQ(other.bucket(3u), map.bucket(11u)); + ASSERT_EQ(other.bucket(3u), other.bucket(11u)); + ASSERT_EQ(*other.begin(3u), *map.begin(3u)); + ASSERT_EQ(other.begin(3u)->first, 11u); + ASSERT_EQ((++other.begin(3u))->first, 3u); +} + +TEST(DenseMap, Move) { + entt::dense_map map; + map.max_load_factor(map.max_load_factor() - .05f); + map.emplace(3u, 42u); + + entt::dense_map other{std::move(map)}; + + ASSERT_EQ(map.size(), 0u); + ASSERT_TRUE(other.contains(3u)); + ASSERT_EQ(map.max_load_factor(), other.max_load_factor()); + + map = other; + map.emplace(1u, 99u); + map.emplace(11u, 77u); + other.emplace(0u, 0u); + other = std::move(map); + + ASSERT_EQ(map.size(), 0u); + ASSERT_TRUE(other.contains(3u)); + ASSERT_TRUE(other.contains(1u)); + ASSERT_TRUE(other.contains(11u)); + ASSERT_FALSE(other.contains(0u)); + + ASSERT_EQ(other[3u], 42u); + ASSERT_EQ(other[1u], 99u); + ASSERT_EQ(other[11u], 77u); + + ASSERT_EQ(other.bucket(3u), other.bucket(11u)); + ASSERT_EQ(other.begin(3u)->first, 11u); + ASSERT_EQ((++other.begin(3u))->first, 3u); +} + +TEST(DenseMap, Iterator) { + using iterator = typename entt::dense_map::iterator; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>>); + static_assert(std::is_same_v>); + + entt::dense_map map; + map.emplace(3, 42); + + iterator end{map.begin()}; + iterator begin{}; + begin = map.end(); + std::swap(begin, end); + + ASSERT_EQ(begin, map.begin()); + ASSERT_EQ(end, map.end()); + ASSERT_NE(begin, end); + + ASSERT_EQ(begin++, map.begin()); + ASSERT_EQ(begin--, map.end()); + + ASSERT_EQ(begin + 1, map.end()); + ASSERT_EQ(end - 1, map.begin()); + + ASSERT_EQ(++begin, map.end()); + ASSERT_EQ(--begin, map.begin()); + + ASSERT_EQ(begin += 1, map.end()); + ASSERT_EQ(begin -= 1, map.begin()); + + ASSERT_EQ(begin + (end - begin), map.end()); + ASSERT_EQ(begin - (begin - end), map.end()); + + ASSERT_EQ(end - (end - begin), map.begin()); + ASSERT_EQ(end + (begin - end), map.begin()); + + ASSERT_EQ(begin[0u].first, map.begin()->first); + ASSERT_EQ(begin[0u].second, (*map.begin()).second); + + ASSERT_LT(begin, end); + ASSERT_LE(begin, map.begin()); + + ASSERT_GT(end, begin); + ASSERT_GE(end, map.end()); + + map.emplace(42, 3); + begin = map.begin(); + + ASSERT_EQ(begin[0u].first, 3); + ASSERT_EQ(begin[1u].second, 3); +} + +TEST(DenseMap, ConstIterator) { + using iterator = typename entt::dense_map::const_iterator; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>>); + static_assert(std::is_same_v>); + + entt::dense_map map; + map.emplace(3, 42); + + iterator cend{map.cbegin()}; + iterator cbegin{}; + cbegin = map.cend(); + std::swap(cbegin, cend); + + ASSERT_EQ(cbegin, map.cbegin()); + ASSERT_EQ(cend, map.cend()); + ASSERT_NE(cbegin, cend); + + ASSERT_EQ(cbegin++, map.cbegin()); + ASSERT_EQ(cbegin--, map.cend()); + + ASSERT_EQ(cbegin + 1, map.cend()); + ASSERT_EQ(cend - 1, map.cbegin()); + + ASSERT_EQ(++cbegin, map.cend()); + ASSERT_EQ(--cbegin, map.cbegin()); + + ASSERT_EQ(cbegin += 1, map.cend()); + ASSERT_EQ(cbegin -= 1, map.cbegin()); + + ASSERT_EQ(cbegin + (cend - cbegin), map.cend()); + ASSERT_EQ(cbegin - (cbegin - cend), map.cend()); + + ASSERT_EQ(cend - (cend - cbegin), map.cbegin()); + ASSERT_EQ(cend + (cbegin - cend), map.cbegin()); + + ASSERT_EQ(cbegin[0u].first, map.cbegin()->first); + ASSERT_EQ(cbegin[0u].second, (*map.cbegin()).second); + + ASSERT_LT(cbegin, cend); + ASSERT_LE(cbegin, map.cbegin()); + + ASSERT_GT(cend, cbegin); + ASSERT_GE(cend, map.cend()); + + map.emplace(42, 3); + cbegin = map.cbegin(); + + ASSERT_EQ(cbegin[0u].first, 3); + ASSERT_EQ(cbegin[1u].second, 3); +} + +TEST(DenseMap, IteratorConversion) { + entt::dense_map map; + map.emplace(3, 42); + + typename entt::dense_map::iterator it = map.begin(); + typename entt::dense_map::const_iterator cit = it; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + + ASSERT_EQ(it->first, 3); + ASSERT_EQ((*it).second, 42); + ASSERT_EQ(it->first, cit->first); + ASSERT_EQ((*it).second, (*cit).second); + + ASSERT_EQ(it - cit, 0); + ASSERT_EQ(cit - it, 0); + ASSERT_LE(it, cit); + ASSERT_LE(cit, it); + ASSERT_GE(it, cit); + ASSERT_GE(cit, it); + ASSERT_EQ(it, cit); + ASSERT_NE(++cit, it); +} + +TEST(DenseMap, Insert) { + entt::dense_map map; + typename entt::dense_map::iterator it; + bool result; + + ASSERT_TRUE(map.empty()); + ASSERT_EQ(map.size(), 0u); + ASSERT_EQ(map.find(0), map.end()); + ASSERT_FALSE(map.contains(0)); + + std::pair value{1, 2}; + std::tie(it, result) = map.insert(std::as_const(value)); + + ASSERT_TRUE(result); + ASSERT_EQ(map.size(), 1u); + ASSERT_EQ(it, --map.end()); + ASSERT_TRUE(map.contains(1)); + ASSERT_NE(map.find(1), map.end()); + ASSERT_EQ(it->first, 1); + ASSERT_EQ(it->second, 2); + + value.second = 99; + std::tie(it, result) = map.insert(value); + + ASSERT_FALSE(result); + ASSERT_EQ(map.size(), 1u); + ASSERT_EQ(it, --map.end()); + ASSERT_EQ(it->second, 2); + + std::tie(it, result) = map.insert(std::pair{3, 4}); + + ASSERT_TRUE(result); + ASSERT_EQ(map.size(), 2u); + ASSERT_EQ(it, --map.end()); + ASSERT_TRUE(map.contains(3)); + ASSERT_NE(map.find(3), map.end()); + ASSERT_EQ(it->first, 3); + ASSERT_EQ(it->second, 4); + + std::tie(it, result) = map.insert(std::pair{3, 99}); + + ASSERT_FALSE(result); + ASSERT_EQ(map.size(), 2u); + ASSERT_EQ(it, --map.end()); + ASSERT_EQ(it->second, 4); + + std::tie(it, result) = map.insert(std::pair{5, 6u}); + + ASSERT_TRUE(result); + ASSERT_EQ(map.size(), 3u); + ASSERT_EQ(it, --map.end()); + ASSERT_TRUE(map.contains(5)); + ASSERT_NE(map.find(5), map.end()); + ASSERT_EQ(it->first, 5); + ASSERT_EQ(it->second, 6); + + std::tie(it, result) = map.insert(std::pair{5, 99u}); + + ASSERT_FALSE(result); + ASSERT_EQ(map.size(), 3u); + ASSERT_EQ(it, --map.end()); + ASSERT_EQ(it->second, 6); + + std::pair range[2u]{std::make_pair(7, 8), std::make_pair(9, 10)}; + map.insert(std::begin(range), std::end(range)); + + ASSERT_EQ(map.size(), 5u); + ASSERT_TRUE(map.contains(7)); + ASSERT_NE(map.find(9), map.end()); + + range[0u].second = 99; + range[1u].second = 99; + map.insert(std::begin(range), std::end(range)); + + ASSERT_EQ(map.size(), 5u); + ASSERT_EQ(map.find(7)->second, 8); + ASSERT_EQ(map.find(9)->second, 10); +} + +TEST(DenseMap, InsertRehash) { + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_map map; + + ASSERT_EQ(map.size(), 0u); + ASSERT_EQ(map.bucket_count(), minimum_bucket_count); + + for(std::size_t next{}; next < minimum_bucket_count; ++next) { + ASSERT_TRUE(map.insert(std::make_pair(next, next)).second); + } + + ASSERT_EQ(map.size(), minimum_bucket_count); + ASSERT_GT(map.bucket_count(), minimum_bucket_count); + ASSERT_TRUE(map.contains(minimum_bucket_count / 2u)); + ASSERT_EQ(map[minimum_bucket_count - 1u], minimum_bucket_count - 1u); + ASSERT_EQ(map.bucket(minimum_bucket_count / 2u), minimum_bucket_count / 2u); + ASSERT_FALSE(map.contains(minimum_bucket_count)); + + ASSERT_TRUE(map.insert(std::make_pair(minimum_bucket_count, minimum_bucket_count)).second); + + ASSERT_EQ(map.size(), minimum_bucket_count + 1u); + ASSERT_EQ(map.bucket_count(), minimum_bucket_count * 2u); + ASSERT_TRUE(map.contains(minimum_bucket_count / 2u)); + ASSERT_EQ(map[minimum_bucket_count - 1u], minimum_bucket_count - 1u); + ASSERT_EQ(map.bucket(minimum_bucket_count / 2u), minimum_bucket_count / 2u); + ASSERT_TRUE(map.contains(minimum_bucket_count)); + + for(std::size_t next{}; next <= minimum_bucket_count; ++next) { + ASSERT_TRUE(map.contains(next)); + ASSERT_EQ(map.bucket(next), next); + ASSERT_EQ(map[next], next); + } +} + +TEST(DenseMap, InsertSameBucket) { + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_map map; + + for(std::size_t next{}; next < minimum_bucket_count; ++next) { + ASSERT_EQ(map.cbegin(next), map.cend(next)); + } + + ASSERT_TRUE(map.insert(std::make_pair(1u, 1u)).second); + ASSERT_TRUE(map.insert(std::make_pair(9u, 9u)).second); + + ASSERT_EQ(map.size(), 2u); + ASSERT_TRUE(map.contains(1u)); + ASSERT_NE(map.find(9u), map.end()); + ASSERT_EQ(map.bucket(1u), 1u); + ASSERT_EQ(map.bucket(9u), 1u); + ASSERT_EQ(map.bucket_size(1u), 2u); + ASSERT_EQ(map.cbegin(6u), map.cend(6u)); +} + +TEST(DenseMap, InsertOrAssign) { + entt::dense_map map; + typename entt::dense_map::iterator it; + bool result; + + ASSERT_TRUE(map.empty()); + ASSERT_EQ(map.size(), 0u); + ASSERT_EQ(map.find(0), map.end()); + ASSERT_FALSE(map.contains(0)); + + const auto key = 1; + std::tie(it, result) = map.insert_or_assign(key, 2); + + ASSERT_TRUE(result); + ASSERT_EQ(map.size(), 1u); + ASSERT_EQ(it, --map.end()); + ASSERT_TRUE(map.contains(1)); + ASSERT_NE(map.find(1), map.end()); + ASSERT_EQ(it->first, 1); + ASSERT_EQ(it->second, 2); + + std::tie(it, result) = map.insert_or_assign(key, 99); + + ASSERT_FALSE(result); + ASSERT_EQ(map.size(), 1u); + ASSERT_EQ(it, --map.end()); + ASSERT_EQ(it->second, 99); + + std::tie(it, result) = map.insert_or_assign(3, 4); + + ASSERT_TRUE(result); + ASSERT_EQ(map.size(), 2u); + ASSERT_EQ(it, --map.end()); + ASSERT_TRUE(map.contains(3)); + ASSERT_NE(map.find(3), map.end()); + ASSERT_EQ(it->first, 3); + ASSERT_EQ(it->second, 4); + + std::tie(it, result) = map.insert_or_assign(3, 99); + + ASSERT_FALSE(result); + ASSERT_EQ(map.size(), 2u); + ASSERT_EQ(it, --map.end()); + ASSERT_EQ(it->second, 99); + + std::tie(it, result) = map.insert_or_assign(5, 6u); + + ASSERT_TRUE(result); + ASSERT_EQ(map.size(), 3u); + ASSERT_EQ(it, --map.end()); + ASSERT_TRUE(map.contains(5)); + ASSERT_NE(map.find(5), map.end()); + ASSERT_EQ(it->first, 5); + ASSERT_EQ(it->second, 6); + + std::tie(it, result) = map.insert_or_assign(5, 99u); + + ASSERT_FALSE(result); + ASSERT_EQ(map.size(), 3u); + ASSERT_EQ(it, --map.end()); + ASSERT_EQ(it->second, 99); +} + +TEST(DenseMap, Emplace) { + entt::dense_map map; + typename entt::dense_map::iterator it; + bool result; + + ASSERT_TRUE(map.empty()); + ASSERT_EQ(map.size(), 0u); + ASSERT_EQ(map.find(0), map.end()); + ASSERT_FALSE(map.contains(0)); + + std::tie(it, result) = map.emplace(); + + ASSERT_TRUE(result); + ASSERT_EQ(map.size(), 1u); + ASSERT_EQ(it, --map.end()); + ASSERT_TRUE(map.contains(0)); + ASSERT_NE(map.find(0), map.end()); + ASSERT_EQ(it->first, 0); + ASSERT_EQ(it->second, 0); + + std::tie(it, result) = map.emplace(); + + ASSERT_FALSE(result); + ASSERT_EQ(map.size(), 1u); + ASSERT_EQ(it, --map.end()); + ASSERT_EQ(it->second, 0); + + std::tie(it, result) = map.emplace(std::make_pair(1, 2)); + + ASSERT_TRUE(result); + ASSERT_EQ(map.size(), 2u); + ASSERT_EQ(it, --map.end()); + ASSERT_TRUE(map.contains(1)); + ASSERT_NE(map.find(1), map.end()); + ASSERT_EQ(it->first, 1); + ASSERT_EQ(it->second, 2); + + std::tie(it, result) = map.emplace(std::make_pair(1, 99)); + + ASSERT_FALSE(result); + ASSERT_EQ(map.size(), 2u); + ASSERT_EQ(it, --map.end()); + ASSERT_EQ(it->second, 2); + + std::tie(it, result) = map.emplace(3, 4); + + ASSERT_TRUE(result); + ASSERT_EQ(map.size(), 3u); + ASSERT_EQ(it, --map.end()); + ASSERT_TRUE(map.contains(3)); + ASSERT_NE(map.find(3), map.end()); + ASSERT_EQ(it->first, 3); + ASSERT_EQ(it->second, 4); + + std::tie(it, result) = map.emplace(3, 99); + + ASSERT_FALSE(result); + ASSERT_EQ(map.size(), 3u); + ASSERT_EQ(it, --map.end()); + ASSERT_EQ(it->second, 4); + + std::tie(it, result) = map.emplace(std::piecewise_construct, std::make_tuple(5), std::make_tuple(6u)); + + ASSERT_TRUE(result); + ASSERT_EQ(map.size(), 4u); + ASSERT_EQ(it, --map.end()); + ASSERT_TRUE(map.contains(5)); + ASSERT_NE(map.find(5), map.end()); + ASSERT_EQ(it->first, 5); + ASSERT_EQ(it->second, 6); + + std::tie(it, result) = map.emplace(std::piecewise_construct, std::make_tuple(5), std::make_tuple(99u)); + + ASSERT_FALSE(result); + ASSERT_EQ(map.size(), 4u); + ASSERT_EQ(it, --map.end()); + ASSERT_EQ(it->second, 6); + + std::tie(it, result) = map.emplace(std::make_pair(1, 99)); + + ASSERT_FALSE(result); + ASSERT_EQ(map.size(), 4u); + ASSERT_EQ(it, ++map.begin()); + ASSERT_EQ(it->second, 2); +} + +TEST(DenseMap, EmplaceRehash) { + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_map map; + + ASSERT_EQ(map.size(), 0u); + ASSERT_EQ(map.bucket_count(), minimum_bucket_count); + + for(std::size_t next{}; next < minimum_bucket_count; ++next) { + ASSERT_TRUE(map.emplace(next, next).second); + ASSERT_LE(map.load_factor(), map.max_load_factor()); + } + + ASSERT_EQ(map.size(), minimum_bucket_count); + ASSERT_GT(map.bucket_count(), minimum_bucket_count); + ASSERT_TRUE(map.contains(minimum_bucket_count / 2u)); + ASSERT_EQ(map[minimum_bucket_count - 1u], minimum_bucket_count - 1u); + ASSERT_EQ(map.bucket(minimum_bucket_count / 2u), minimum_bucket_count / 2u); + ASSERT_FALSE(map.contains(minimum_bucket_count)); + + ASSERT_TRUE(map.emplace(minimum_bucket_count, minimum_bucket_count).second); + + ASSERT_EQ(map.size(), minimum_bucket_count + 1u); + ASSERT_EQ(map.bucket_count(), minimum_bucket_count * 2u); + ASSERT_TRUE(map.contains(minimum_bucket_count / 2u)); + ASSERT_EQ(map[minimum_bucket_count - 1u], minimum_bucket_count - 1u); + ASSERT_EQ(map.bucket(minimum_bucket_count / 2u), minimum_bucket_count / 2u); + ASSERT_TRUE(map.contains(minimum_bucket_count)); + + for(std::size_t next{}; next <= minimum_bucket_count; ++next) { + ASSERT_TRUE(map.contains(next)); + ASSERT_EQ(map.bucket(next), next); + ASSERT_EQ(map[next], next); + } +} + +TEST(DenseMap, EmplaceSameBucket) { + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_map map; + + for(std::size_t next{}; next < minimum_bucket_count; ++next) { + ASSERT_EQ(map.cbegin(next), map.cend(next)); + } + + ASSERT_TRUE(map.emplace(1u, 1u).second); + ASSERT_TRUE(map.emplace(9u, 9u).second); + + ASSERT_EQ(map.size(), 2u); + ASSERT_TRUE(map.contains(1u)); + ASSERT_NE(map.find(9u), map.end()); + ASSERT_EQ(map.bucket(1u), 1u); + ASSERT_EQ(map.bucket(9u), 1u); + ASSERT_EQ(map.bucket_size(1u), 2u); + ASSERT_EQ(map.cbegin(6u), map.cend(6u)); +} + +TEST(DenseMap, TryEmplace) { + entt::dense_map map; + typename entt::dense_map::iterator it; + bool result; + + ASSERT_TRUE(map.empty()); + ASSERT_EQ(map.size(), 0u); + ASSERT_EQ(map.find(1), map.end()); + ASSERT_FALSE(map.contains(1)); + + std::tie(it, result) = map.try_emplace(1, 2); + + ASSERT_TRUE(result); + ASSERT_EQ(map.size(), 1u); + ASSERT_EQ(it, --map.end()); + ASSERT_TRUE(map.contains(1)); + ASSERT_NE(map.find(1), map.end()); + ASSERT_EQ(it->first, 1); + ASSERT_EQ(it->second, 2); + + std::tie(it, result) = map.try_emplace(1, 99); + + ASSERT_FALSE(result); + ASSERT_EQ(map.size(), 1u); + ASSERT_EQ(it, --map.end()); + ASSERT_EQ(it->second, 2); +} + +TEST(DenseMap, TryEmplaceRehash) { + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_map map; + + ASSERT_EQ(map.size(), 0u); + ASSERT_EQ(map.bucket_count(), minimum_bucket_count); + + for(std::size_t next{}; next < minimum_bucket_count; ++next) { + ASSERT_TRUE(map.try_emplace(next, next).second); + } + + ASSERT_EQ(map.size(), minimum_bucket_count); + ASSERT_GT(map.bucket_count(), minimum_bucket_count); + ASSERT_TRUE(map.contains(minimum_bucket_count / 2u)); + ASSERT_EQ(map[minimum_bucket_count - 1u], minimum_bucket_count - 1u); + ASSERT_EQ(map.bucket(minimum_bucket_count / 2u), minimum_bucket_count / 2u); + ASSERT_FALSE(map.contains(minimum_bucket_count)); + + ASSERT_TRUE(map.try_emplace(minimum_bucket_count, minimum_bucket_count).second); + + ASSERT_EQ(map.size(), minimum_bucket_count + 1u); + ASSERT_EQ(map.bucket_count(), minimum_bucket_count * 2u); + ASSERT_TRUE(map.contains(minimum_bucket_count / 2u)); + ASSERT_EQ(map[minimum_bucket_count - 1u], minimum_bucket_count - 1u); + ASSERT_EQ(map.bucket(minimum_bucket_count / 2u), minimum_bucket_count / 2u); + ASSERT_TRUE(map.contains(minimum_bucket_count)); + + for(std::size_t next{}; next <= minimum_bucket_count; ++next) { + ASSERT_TRUE(map.contains(next)); + ASSERT_EQ(map.bucket(next), next); + ASSERT_EQ(map[next], next); + } +} + +TEST(DenseMap, TryEmplaceSameBucket) { + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_map map; + + for(std::size_t next{}; next < minimum_bucket_count; ++next) { + ASSERT_EQ(map.cbegin(next), map.cend(next)); + } + + ASSERT_TRUE(map.try_emplace(1u, 1u).second); + ASSERT_TRUE(map.try_emplace(9u, 9u).second); + + ASSERT_EQ(map.size(), 2u); + ASSERT_TRUE(map.contains(1u)); + ASSERT_NE(map.find(9u), map.end()); + ASSERT_EQ(map.bucket(1u), 1u); + ASSERT_EQ(map.bucket(9u), 1u); + ASSERT_EQ(map.bucket_size(1u), 2u); + ASSERT_EQ(map.cbegin(6u), map.cend(6u)); +} + +TEST(DenseMap, TryEmplaceMovableType) { + entt::dense_map> map; + std::unique_ptr value = std::make_unique(42); + + ASSERT_TRUE(map.try_emplace(*value, std::move(value)).second); + ASSERT_FALSE(map.empty()); + ASSERT_FALSE(value); + + value = std::make_unique(42); + + ASSERT_FALSE(map.try_emplace(*value, std::move(value)).second); + ASSERT_TRUE(value); +} + +TEST(DenseMap, Erase) { + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_map map; + + for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) { + map.emplace(next, next); + } + + ASSERT_EQ(map.bucket_count(), 2 * minimum_bucket_count); + ASSERT_EQ(map.size(), minimum_bucket_count + 1u); + + for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) { + ASSERT_TRUE(map.contains(next)); + } + + auto it = map.erase(++map.begin()); + it = map.erase(it, it + 1); + + ASSERT_EQ((--map.end())->first, 6u); + ASSERT_EQ(map.erase(6u), 1u); + ASSERT_EQ(map.erase(6u), 0u); + + ASSERT_EQ(map.bucket_count(), 2 * minimum_bucket_count); + ASSERT_EQ(map.size(), minimum_bucket_count + 1u - 3u); + + ASSERT_EQ(it, ++map.begin()); + ASSERT_EQ(it->first, 7u); + ASSERT_EQ((--map.end())->first, 5u); + + for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) { + if(next == 1u || next == 8u || next == 6u) { + ASSERT_FALSE(map.contains(next)); + ASSERT_EQ(map.bucket_size(next), 0u); + } else { + ASSERT_TRUE(map.contains(next)); + ASSERT_EQ(map.bucket(next), next); + ASSERT_EQ(map.bucket_size(next), 1u); + } + } + + map.erase(map.begin(), map.end()); + + for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) { + ASSERT_FALSE(map.contains(next)); + } + + ASSERT_EQ(map.bucket_count(), 2 * minimum_bucket_count); + ASSERT_EQ(map.size(), 0u); +} + +TEST(DenseMap, EraseFromBucket) { + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_map map; + + ASSERT_EQ(map.bucket_count(), minimum_bucket_count); + ASSERT_EQ(map.size(), 0u); + + for(std::size_t next{}; next < 4u; ++next) { + ASSERT_TRUE(map.emplace(2u * minimum_bucket_count * next, 2u * minimum_bucket_count * next).second); + ASSERT_TRUE(map.emplace(2u * minimum_bucket_count * next + 2u, 2u * minimum_bucket_count * next + 2u).second); + ASSERT_TRUE(map.emplace(2u * minimum_bucket_count * (next + 1u) - 1u, 2u * minimum_bucket_count * (next + 1u) - 1u).second); + } + + ASSERT_EQ(map.bucket_count(), 2u * minimum_bucket_count); + ASSERT_EQ(map.size(), 12u); + + ASSERT_EQ(map.bucket_size(0u), 4u); + ASSERT_EQ(map.bucket_size(2u), 4u); + ASSERT_EQ(map.bucket_size(15u), 4u); + + map.erase(map.end() - 3, map.end()); + + ASSERT_EQ(map.bucket_count(), 2u * minimum_bucket_count); + ASSERT_EQ(map.size(), 9u); + + ASSERT_EQ(map.bucket_size(0u), 3u); + ASSERT_EQ(map.bucket_size(2u), 3u); + ASSERT_EQ(map.bucket_size(15u), 3u); + + for(std::size_t next{}; next < 3u; ++next) { + ASSERT_TRUE(map.contains(2u * minimum_bucket_count * next)); + ASSERT_EQ(map.bucket(2u * minimum_bucket_count * next), 0u); + + ASSERT_TRUE(map.contains(2u * minimum_bucket_count * next + 2u)); + ASSERT_EQ(map.bucket(2u * minimum_bucket_count * next + 2u), 2u); + + ASSERT_TRUE(map.contains(2u * minimum_bucket_count * (next + 1u) - 1u)); + ASSERT_EQ(map.bucket(2u * minimum_bucket_count * (next + 1u) - 1u), 15u); + } + + ASSERT_FALSE(map.contains(2u * minimum_bucket_count * 3u)); + ASSERT_FALSE(map.contains(2u * minimum_bucket_count * 3u + 2u)); + ASSERT_FALSE(map.contains(2u * minimum_bucket_count * (3u + 1u) - 1u)); + + map.erase((++map.begin(0u))->first); + map.erase((++map.begin(2u))->first); + map.erase((++map.begin(15u))->first); + + ASSERT_EQ(map.bucket_count(), 2u * minimum_bucket_count); + ASSERT_EQ(map.size(), 6u); + + ASSERT_EQ(map.bucket_size(0u), 2u); + ASSERT_EQ(map.bucket_size(2u), 2u); + ASSERT_EQ(map.bucket_size(15u), 2u); + + ASSERT_FALSE(map.contains(2u * minimum_bucket_count * 1u)); + ASSERT_FALSE(map.contains(2u * minimum_bucket_count * 1u + 2u)); + ASSERT_FALSE(map.contains(2u * minimum_bucket_count * (1u + 1u) - 1u)); + + while(map.begin(15) != map.end(15u)) { + map.erase(map.begin(15)->first); + } + + ASSERT_EQ(map.bucket_count(), 2u * minimum_bucket_count); + ASSERT_EQ(map.size(), 4u); + + ASSERT_EQ(map.bucket_size(0u), 2u); + ASSERT_EQ(map.bucket_size(2u), 2u); + ASSERT_EQ(map.bucket_size(15u), 0u); + + ASSERT_TRUE(map.contains(0u * minimum_bucket_count)); + ASSERT_TRUE(map.contains(0u * minimum_bucket_count + 2u)); + ASSERT_TRUE(map.contains(4u * minimum_bucket_count)); + ASSERT_TRUE(map.contains(4u * minimum_bucket_count + 2u)); + + map.erase(4u * minimum_bucket_count + 2u); + map.erase(0u * minimum_bucket_count); + + ASSERT_EQ(map.bucket_count(), 2u * minimum_bucket_count); + ASSERT_EQ(map.size(), 2u); + + ASSERT_EQ(map.bucket_size(0u), 1u); + ASSERT_EQ(map.bucket_size(2u), 1u); + ASSERT_EQ(map.bucket_size(15u), 0u); + + ASSERT_FALSE(map.contains(0u * minimum_bucket_count)); + ASSERT_TRUE(map.contains(0u * minimum_bucket_count + 2u)); + ASSERT_TRUE(map.contains(4u * minimum_bucket_count)); + ASSERT_FALSE(map.contains(4u * minimum_bucket_count + 2u)); +} + +TEST(DenseMap, Swap) { + entt::dense_map map; + entt::dense_map other; + + map.emplace(0, 1); + + ASSERT_FALSE(map.empty()); + ASSERT_TRUE(other.empty()); + ASSERT_TRUE(map.contains(0)); + ASSERT_FALSE(other.contains(0)); + + map.swap(other); + + ASSERT_TRUE(map.empty()); + ASSERT_FALSE(other.empty()); + ASSERT_FALSE(map.contains(0)); + ASSERT_TRUE(other.contains(0)); +} + +TEST(DenseMap, Indexing) { + entt::dense_map map; + const auto key = 1; + + ASSERT_FALSE(map.contains(key)); + + map[key] = 99; + + ASSERT_TRUE(map.contains(key)); + ASSERT_EQ(map[std::move(key)], 99); + ASSERT_EQ(std::as_const(map).at(key), 99); + ASSERT_EQ(map.at(key), 99); +} + +TEST(DenseMapDeathTest, Indexing) { + entt::dense_map map; + + ASSERT_DEATH([[maybe_unused]] auto value = std::as_const(map).at(0), ""); + ASSERT_DEATH([[maybe_unused]] auto value = map.at(42), ""); +} + +TEST(DenseMap, LocalIterator) { + using iterator = typename entt::dense_map::local_iterator; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>>); + static_assert(std::is_same_v>); + + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_map map; + map.emplace(3u, 42u); + map.emplace(3u + minimum_bucket_count, 99u); + + iterator end{map.begin(3u)}; + iterator begin{}; + begin = map.end(3u); + std::swap(begin, end); + + ASSERT_EQ(begin, map.begin(3u)); + ASSERT_EQ(end, map.end(3u)); + ASSERT_NE(begin, end); + + ASSERT_EQ(begin->first, 3u + minimum_bucket_count); + ASSERT_EQ((*begin).second, 99u); + + ASSERT_EQ(begin++, map.begin(3u)); + ASSERT_EQ(++begin, map.end(3u)); +} + +TEST(DenseMap, ConstLocalIterator) { + using iterator = typename entt::dense_map::const_local_iterator; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>>); + static_assert(std::is_same_v>); + + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_map map; + map.emplace(3u, 42u); + map.emplace(3u + minimum_bucket_count, 99u); + + iterator cend{map.begin(3u)}; + iterator cbegin{}; + cbegin = map.end(3u); + std::swap(cbegin, cend); + + ASSERT_EQ(cbegin, map.begin(3u)); + ASSERT_EQ(cend, map.end(3u)); + ASSERT_NE(cbegin, cend); + + ASSERT_EQ(cbegin->first, 3u + minimum_bucket_count); + ASSERT_EQ((*cbegin).second, 99u); + + ASSERT_EQ(cbegin++, map.begin(3u)); + ASSERT_EQ(++cbegin, map.end(3u)); +} + +TEST(DenseMap, LocalIteratorConversion) { + entt::dense_map map; + map.emplace(3, 42); + + typename entt::dense_map::local_iterator it = map.begin(map.bucket(3)); + typename entt::dense_map::const_local_iterator cit = it; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + + ASSERT_EQ(it->first, 3); + ASSERT_EQ((*it).second, 42); + ASSERT_EQ(it->first, cit->first); + ASSERT_EQ((*it).second, (*cit).second); + + ASSERT_EQ(it, cit); + ASSERT_NE(++cit, it); +} + +TEST(DenseMap, Rehash) { + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_map map; + map[32u] = 99u; + + ASSERT_EQ(map.bucket_count(), minimum_bucket_count); + ASSERT_TRUE(map.contains(32u)); + ASSERT_EQ(map.bucket(32u), 0u); + ASSERT_EQ(map[32u], 99u); + + map.rehash(12u); + + ASSERT_EQ(map.bucket_count(), 2u * minimum_bucket_count); + ASSERT_TRUE(map.contains(32u)); + ASSERT_EQ(map.bucket(32u), 0u); + ASSERT_EQ(map[32u], 99u); + + map.rehash(44u); + + ASSERT_EQ(map.bucket_count(), 8u * minimum_bucket_count); + ASSERT_TRUE(map.contains(32u)); + ASSERT_EQ(map.bucket(32u), 32u); + ASSERT_EQ(map[32u], 99u); + + map.rehash(0u); + + ASSERT_EQ(map.bucket_count(), minimum_bucket_count); + ASSERT_TRUE(map.contains(32u)); + ASSERT_EQ(map.bucket(32u), 0u); + ASSERT_EQ(map[32u], 99u); + + for(std::size_t next{}; next < minimum_bucket_count; ++next) { + map.emplace(next, next); + } + + ASSERT_EQ(map.size(), minimum_bucket_count + 1u); + ASSERT_EQ(map.bucket_count(), 2u * minimum_bucket_count); + + map.rehash(0u); + + ASSERT_EQ(map.bucket_count(), 2u * minimum_bucket_count); + ASSERT_TRUE(map.contains(32u)); + + map.rehash(55u); + + ASSERT_EQ(map.bucket_count(), 8u * minimum_bucket_count); + ASSERT_TRUE(map.contains(32u)); + + map.rehash(2u); + + ASSERT_EQ(map.bucket_count(), 2u * minimum_bucket_count); + ASSERT_TRUE(map.contains(32u)); + ASSERT_EQ(map.bucket(32u), 0u); + ASSERT_EQ(map[32u], 99u); + + for(std::size_t next{}; next < minimum_bucket_count; ++next) { + ASSERT_TRUE(map.contains(next)); + ASSERT_EQ(map[next], next); + ASSERT_EQ(map.bucket(next), next); + } + + ASSERT_EQ(map.bucket_size(0u), 2u); + ASSERT_EQ(map.bucket_size(3u), 1u); + + ASSERT_EQ(map.begin(0u)->first, 0u); + ASSERT_EQ(map.begin(0u)->second, 0u); + ASSERT_EQ((++map.begin(0u))->first, 32u); + ASSERT_EQ((++map.begin(0u))->second, 99u); + + map.clear(); + map.rehash(2u); + + ASSERT_EQ(map.bucket_count(), minimum_bucket_count); + ASSERT_FALSE(map.contains(32u)); + + for(std::size_t next{}; next < minimum_bucket_count; ++next) { + ASSERT_FALSE(map.contains(next)); + } + + ASSERT_EQ(map.bucket_size(0u), 0u); + ASSERT_EQ(map.bucket_size(3u), 0u); +} + +TEST(DenseMap, Reserve) { + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_map map; + + ASSERT_EQ(map.bucket_count(), minimum_bucket_count); + + map.reserve(0u); + + ASSERT_EQ(map.bucket_count(), minimum_bucket_count); + + map.reserve(minimum_bucket_count); + + ASSERT_EQ(map.bucket_count(), 2 * minimum_bucket_count); + ASSERT_EQ(map.bucket_count(), entt::next_power_of_two(std::ceil(minimum_bucket_count / map.max_load_factor()))); +} + +TEST(DenseMap, ThrowingAllocator) { + using allocator = test::throwing_allocator>; + using packed_allocator = test::throwing_allocator>; + using packed_exception = typename packed_allocator::exception_type; + + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_map, std::equal_to, allocator> map{}; + + packed_allocator::trigger_on_allocate = true; + + ASSERT_EQ(map.bucket_count(), minimum_bucket_count); + ASSERT_THROW(map.reserve(2u * map.bucket_count()), packed_exception); + ASSERT_EQ(map.bucket_count(), minimum_bucket_count); + + packed_allocator::trigger_on_allocate = true; + + ASSERT_THROW(map.emplace(0u, 0u), packed_exception); + ASSERT_FALSE(map.contains(0u)); + + packed_allocator::trigger_on_allocate = true; + + ASSERT_THROW(map.emplace(std::piecewise_construct, std::make_tuple(0u), std::make_tuple(0u)), packed_exception); + ASSERT_FALSE(map.contains(0u)); + + packed_allocator::trigger_on_allocate = true; + + ASSERT_THROW(map.insert_or_assign(0u, 0u), packed_exception); + ASSERT_FALSE(map.contains(0u)); +} + +#if defined(ENTT_HAS_TRACKED_MEMORY_RESOURCE) + +TEST(DenseMap, NoUsesAllocatorConstruction) { + using allocator = std::pmr::polymorphic_allocator>; + + test::tracked_memory_resource memory_resource{}; + entt::dense_map, std::equal_to, allocator> map{&memory_resource}; + + map.reserve(1u); + memory_resource.reset(); + map.emplace(0, 0); + + ASSERT_TRUE(map.get_allocator().resource()->is_equal(memory_resource)); + ASSERT_EQ(memory_resource.do_allocate_counter(), 0u); + ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u); +} + +TEST(DenseMap, KeyUsesAllocatorConstruction) { + using string_type = typename test::tracked_memory_resource::string_type; + using allocator = std::pmr::polymorphic_allocator>; + + test::tracked_memory_resource memory_resource{}; + entt::dense_map, std::equal_to, allocator> map{&memory_resource}; + + map.reserve(1u); + memory_resource.reset(); + map.emplace(test::tracked_memory_resource::default_value, 0); + + ASSERT_TRUE(map.get_allocator().resource()->is_equal(memory_resource)); + ASSERT_GT(memory_resource.do_allocate_counter(), 0u); + ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u); + + memory_resource.reset(); + decltype(map) other{map, &memory_resource}; + + ASSERT_TRUE(memory_resource.is_equal(*other.get_allocator().resource())); + ASSERT_GT(memory_resource.do_allocate_counter(), 0u); + ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u); +} + +TEST(DenseMap, ValueUsesAllocatorConstruction) { + using string_type = typename test::tracked_memory_resource::string_type; + using allocator = std::pmr::polymorphic_allocator>; + + test::tracked_memory_resource memory_resource{}; + entt::dense_map, std::equal_to, allocator> map{std::pmr::get_default_resource()}; + + map.reserve(1u); + memory_resource.reset(); + map.emplace(0, test::tracked_memory_resource::default_value); + + ASSERT_FALSE(map.get_allocator().resource()->is_equal(memory_resource)); + ASSERT_EQ(memory_resource.do_allocate_counter(), 0u); + ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u); + + decltype(map) other{std::move(map), &memory_resource}; + + ASSERT_TRUE(other.get_allocator().resource()->is_equal(memory_resource)); + ASSERT_GT(memory_resource.do_allocate_counter(), 0u); + ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u); +} + +#endif diff --git a/modules/entt/test/entt/container/dense_set.cpp b/modules/entt/test/entt/container/dense_set.cpp new file mode 100644 index 0000000..b6d1a78 --- /dev/null +++ b/modules/entt/test/entt/container/dense_set.cpp @@ -0,0 +1,885 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../common/throwing_allocator.hpp" +#include "../common/tracked_memory_resource.hpp" + +struct transparent_equal_to { + using is_transparent = void; + + template + constexpr std::enable_if_t, bool> + operator()(const Type &lhs, const Other &rhs) const { + return lhs == static_cast(rhs); + } +}; + +TEST(DenseSet, Functionalities) { + entt::dense_set set; + + ASSERT_NO_THROW([[maybe_unused]] auto alloc = set.get_allocator()); + + ASSERT_TRUE(set.empty()); + ASSERT_EQ(set.size(), 0u); + ASSERT_EQ(set.load_factor(), 0.f); + ASSERT_EQ(set.max_load_factor(), .875f); + + set.max_load_factor(.9f); + + ASSERT_EQ(set.max_load_factor(), .9f); + + ASSERT_EQ(set.begin(), set.end()); + ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end()); + ASSERT_EQ(set.cbegin(), set.cend()); + + ASSERT_NE(set.max_bucket_count(), 0u); + ASSERT_EQ(set.bucket_count(), 8u); + ASSERT_EQ(set.bucket_size(3u), 0u); + + ASSERT_EQ(set.bucket(0), 0u); + ASSERT_EQ(set.bucket(3), 3u); + ASSERT_EQ(set.bucket(8), 0u); + ASSERT_EQ(set.bucket(10), 2u); + + ASSERT_EQ(set.begin(1u), set.end(1u)); + ASSERT_EQ(std::as_const(set).begin(1u), std::as_const(set).end(1u)); + ASSERT_EQ(set.cbegin(1u), set.cend(1u)); + + ASSERT_FALSE(set.contains(42)); + ASSERT_FALSE(set.contains(4.2)); + + ASSERT_EQ(set.find(42), set.end()); + ASSERT_EQ(set.find(4.2), set.end()); + ASSERT_EQ(std::as_const(set).find(42), set.cend()); + ASSERT_EQ(std::as_const(set).find(4.2), set.cend()); + + ASSERT_EQ(set.hash_function()(42), 42); + ASSERT_TRUE(set.key_eq()(42, 42)); + + set.emplace(0u); + + ASSERT_FALSE(set.empty()); + ASSERT_EQ(set.size(), 1u); + + ASSERT_NE(set.begin(), set.end()); + ASSERT_NE(std::as_const(set).begin(), std::as_const(set).end()); + ASSERT_NE(set.cbegin(), set.cend()); + + ASSERT_TRUE(set.contains(0u)); + ASSERT_EQ(set.bucket(0u), 0u); + + set.clear(); + + ASSERT_TRUE(set.empty()); + ASSERT_EQ(set.size(), 0u); + + ASSERT_EQ(set.begin(), set.end()); + ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end()); + ASSERT_EQ(set.cbegin(), set.cend()); + + ASSERT_FALSE(set.contains(0u)); +} + +TEST(DenseSet, Constructors) { + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_set set; + + ASSERT_EQ(set.bucket_count(), minimum_bucket_count); + + set = entt::dense_set{std::allocator{}}; + set = entt::dense_set{2u * minimum_bucket_count, std::allocator{}}; + set = entt::dense_set{4u * minimum_bucket_count, std::hash(), std::allocator{}}; + + set.emplace(3); + + entt::dense_set temp{set, set.get_allocator()}; + entt::dense_set other{std::move(temp), set.get_allocator()}; + + ASSERT_EQ(set.size(), 1u); + ASSERT_EQ(other.size(), 1u); + ASSERT_EQ(set.bucket_count(), 4u * minimum_bucket_count); + ASSERT_EQ(other.bucket_count(), 4u * minimum_bucket_count); +} + +TEST(DenseSet, Copy) { + entt::dense_set set; + set.max_load_factor(set.max_load_factor() - .05f); + set.emplace(3u); + + entt::dense_set other{set}; + + ASSERT_TRUE(set.contains(3u)); + ASSERT_TRUE(other.contains(3u)); + ASSERT_EQ(set.max_load_factor(), other.max_load_factor()); + + set.emplace(1u); + set.emplace(11u); + other.emplace(0u); + other = set; + + ASSERT_TRUE(other.contains(3u)); + ASSERT_TRUE(other.contains(1u)); + ASSERT_TRUE(other.contains(11u)); + ASSERT_FALSE(other.contains(0u)); + + ASSERT_EQ(other.bucket(3u), set.bucket(11u)); + ASSERT_EQ(other.bucket(3u), other.bucket(11u)); + ASSERT_EQ(*other.begin(3u), *set.begin(3u)); + ASSERT_EQ(*other.begin(3u), 11u); + ASSERT_EQ((*++other.begin(3u)), 3u); +} + +TEST(DenseSet, Move) { + entt::dense_set set; + set.max_load_factor(set.max_load_factor() - .05f); + set.emplace(3u); + + entt::dense_set other{std::move(set)}; + + ASSERT_EQ(set.size(), 0u); + ASSERT_TRUE(other.contains(3u)); + ASSERT_EQ(set.max_load_factor(), other.max_load_factor()); + + set = other; + set.emplace(1u); + set.emplace(11u); + other.emplace(0u); + other = std::move(set); + + ASSERT_EQ(set.size(), 0u); + ASSERT_TRUE(other.contains(3u)); + ASSERT_TRUE(other.contains(1u)); + ASSERT_TRUE(other.contains(11u)); + ASSERT_FALSE(other.contains(0u)); + + ASSERT_EQ(other.bucket(3u), other.bucket(11u)); + ASSERT_EQ(*other.begin(3u), 11u); + ASSERT_EQ(*++other.begin(3u), 3u); +} + +TEST(DenseSet, Iterator) { + using iterator = typename entt::dense_set::iterator; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + entt::dense_set set; + set.emplace(3); + + iterator end{set.begin()}; + iterator begin{}; + begin = set.end(); + std::swap(begin, end); + + ASSERT_EQ(begin, set.begin()); + ASSERT_EQ(end, set.end()); + ASSERT_NE(begin, end); + + ASSERT_EQ(begin++, set.begin()); + ASSERT_EQ(begin--, set.end()); + + ASSERT_EQ(begin + 1, set.end()); + ASSERT_EQ(end - 1, set.begin()); + + ASSERT_EQ(++begin, set.end()); + ASSERT_EQ(--begin, set.begin()); + + ASSERT_EQ(begin += 1, set.end()); + ASSERT_EQ(begin -= 1, set.begin()); + + ASSERT_EQ(begin + (end - begin), set.end()); + ASSERT_EQ(begin - (begin - end), set.end()); + + ASSERT_EQ(end - (end - begin), set.begin()); + ASSERT_EQ(end + (begin - end), set.begin()); + + ASSERT_EQ(begin[0u], *set.begin().operator->()); + ASSERT_EQ(begin[0u], *set.begin()); + + ASSERT_LT(begin, end); + ASSERT_LE(begin, set.begin()); + + ASSERT_GT(end, begin); + ASSERT_GE(end, set.end()); + + set.emplace(42); + begin = set.begin(); + + ASSERT_EQ(begin[0u], 3); + ASSERT_EQ(begin[1u], 42); +} + +TEST(DenseSet, ConstIterator) { + using iterator = typename entt::dense_set::const_iterator; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + entt::dense_set set; + set.emplace(3); + + iterator cend{set.cbegin()}; + iterator cbegin{}; + cbegin = set.cend(); + std::swap(cbegin, cend); + + ASSERT_EQ(cbegin, set.cbegin()); + ASSERT_EQ(cend, set.cend()); + ASSERT_NE(cbegin, cend); + + ASSERT_EQ(cbegin++, set.cbegin()); + ASSERT_EQ(cbegin--, set.cend()); + + ASSERT_EQ(cbegin + 1, set.cend()); + ASSERT_EQ(cend - 1, set.cbegin()); + + ASSERT_EQ(++cbegin, set.cend()); + ASSERT_EQ(--cbegin, set.cbegin()); + + ASSERT_EQ(cbegin += 1, set.cend()); + ASSERT_EQ(cbegin -= 1, set.cbegin()); + + ASSERT_EQ(cbegin + (cend - cbegin), set.cend()); + ASSERT_EQ(cbegin - (cbegin - cend), set.cend()); + + ASSERT_EQ(cend - (cend - cbegin), set.cbegin()); + ASSERT_EQ(cend + (cbegin - cend), set.cbegin()); + + ASSERT_EQ(cbegin[0u], *set.cbegin().operator->()); + ASSERT_EQ(cbegin[0u], *set.cbegin()); + + ASSERT_LT(cbegin, cend); + ASSERT_LE(cbegin, set.cbegin()); + + ASSERT_GT(cend, cbegin); + ASSERT_GE(cend, set.cend()); + + set.emplace(42); + cbegin = set.cbegin(); + + ASSERT_EQ(cbegin[0u], 3); + ASSERT_EQ(cbegin[1u], 42); +} + +TEST(DenseSet, IteratorConversion) { + entt::dense_set set; + set.emplace(3); + + typename entt::dense_set::iterator it = set.begin(); + typename entt::dense_set::const_iterator cit = it; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + ASSERT_EQ(*it, 3); + ASSERT_EQ(*it.operator->(), 3); + ASSERT_EQ(it.operator->(), cit.operator->()); + ASSERT_EQ(*it, *cit); + + ASSERT_EQ(it - cit, 0); + ASSERT_EQ(cit - it, 0); + ASSERT_LE(it, cit); + ASSERT_LE(cit, it); + ASSERT_GE(it, cit); + ASSERT_GE(cit, it); + ASSERT_EQ(it, cit); + ASSERT_NE(++cit, it); +} + +TEST(DenseSet, Insert) { + entt::dense_set set; + typename entt::dense_set::iterator it; + bool result; + + ASSERT_TRUE(set.empty()); + ASSERT_EQ(set.size(), 0u); + ASSERT_EQ(set.find(0), set.end()); + ASSERT_FALSE(set.contains(0)); + + int value{1}; + std::tie(it, result) = set.insert(std::as_const(value)); + + ASSERT_TRUE(result); + ASSERT_EQ(set.size(), 1u); + ASSERT_EQ(it, --set.end()); + ASSERT_TRUE(set.contains(1)); + ASSERT_NE(set.find(1), set.end()); + ASSERT_EQ(*it, 1); + + std::tie(it, result) = set.insert(value); + + ASSERT_FALSE(result); + ASSERT_EQ(set.size(), 1u); + ASSERT_EQ(it, --set.end()); + ASSERT_EQ(*it, 1); + + std::tie(it, result) = set.insert(3); + + ASSERT_TRUE(result); + ASSERT_EQ(set.size(), 2u); + ASSERT_EQ(it, --set.end()); + ASSERT_TRUE(set.contains(3)); + ASSERT_NE(set.find(3), set.end()); + ASSERT_EQ(*it, 3); + + std::tie(it, result) = set.insert(3); + + ASSERT_FALSE(result); + ASSERT_EQ(set.size(), 2u); + ASSERT_EQ(it, --set.end()); + ASSERT_EQ(*it, 3); + + int range[2u]{7, 9}; + set.insert(std::begin(range), std::end(range)); + + ASSERT_EQ(set.size(), 4u); + ASSERT_TRUE(set.contains(7)); + ASSERT_NE(set.find(9), set.end()); +} + +TEST(DenseSet, InsertRehash) { + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_set set; + + ASSERT_EQ(set.size(), 0u); + ASSERT_EQ(set.bucket_count(), minimum_bucket_count); + + for(std::size_t next{}; next < minimum_bucket_count; ++next) { + ASSERT_TRUE(set.insert(next).second); + } + + ASSERT_EQ(set.size(), minimum_bucket_count); + ASSERT_GT(set.bucket_count(), minimum_bucket_count); + ASSERT_TRUE(set.contains(minimum_bucket_count / 2u)); + ASSERT_EQ(set.bucket(minimum_bucket_count / 2u), minimum_bucket_count / 2u); + ASSERT_FALSE(set.contains(minimum_bucket_count)); + + ASSERT_TRUE(set.insert(minimum_bucket_count).second); + + ASSERT_EQ(set.size(), minimum_bucket_count + 1u); + ASSERT_EQ(set.bucket_count(), minimum_bucket_count * 2u); + ASSERT_TRUE(set.contains(minimum_bucket_count / 2u)); + ASSERT_EQ(set.bucket(minimum_bucket_count / 2u), minimum_bucket_count / 2u); + ASSERT_TRUE(set.contains(minimum_bucket_count)); + + for(std::size_t next{}; next <= minimum_bucket_count; ++next) { + ASSERT_TRUE(set.contains(next)); + ASSERT_EQ(set.bucket(next), next); + } +} + +TEST(DenseSet, InsertSameBucket) { + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_set set; + + for(std::size_t next{}; next < minimum_bucket_count; ++next) { + ASSERT_EQ(set.cbegin(next), set.cend(next)); + } + + ASSERT_TRUE(set.insert(1u).second); + ASSERT_TRUE(set.insert(9u).second); + + ASSERT_EQ(set.size(), 2u); + ASSERT_TRUE(set.contains(1u)); + ASSERT_NE(set.find(9u), set.end()); + ASSERT_EQ(set.bucket(1u), 1u); + ASSERT_EQ(set.bucket(9u), 1u); + ASSERT_EQ(set.bucket_size(1u), 2u); + ASSERT_EQ(set.cbegin(6u), set.cend(6u)); +} + +TEST(DenseSet, Emplace) { + entt::dense_set set; + typename entt::dense_set::iterator it; + bool result; + + ASSERT_TRUE(set.empty()); + ASSERT_EQ(set.size(), 0u); + ASSERT_EQ(set.find(0), set.end()); + ASSERT_FALSE(set.contains(0)); + + std::tie(it, result) = set.emplace(); + + ASSERT_TRUE(result); + ASSERT_EQ(set.size(), 1u); + ASSERT_EQ(it, --set.end()); + ASSERT_TRUE(set.contains(0)); + ASSERT_NE(set.find(0), set.end()); + ASSERT_EQ(*it, 0); + + std::tie(it, result) = set.emplace(); + + ASSERT_FALSE(result); + ASSERT_EQ(set.size(), 1u); + ASSERT_EQ(it, --set.end()); + ASSERT_EQ(*it, 0); + + std::tie(it, result) = set.emplace(1); + + ASSERT_TRUE(result); + ASSERT_EQ(set.size(), 2u); + ASSERT_EQ(it, --set.end()); + ASSERT_TRUE(set.contains(1)); + ASSERT_NE(set.find(1), set.end()); + ASSERT_EQ(*it, 1); + + std::tie(it, result) = set.emplace(1); + + ASSERT_FALSE(result); + ASSERT_EQ(set.size(), 2u); + ASSERT_EQ(it, --set.end()); + ASSERT_EQ(*it, 1); +} + +TEST(DenseSet, EmplaceRehash) { + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_set set; + + ASSERT_EQ(set.size(), 0u); + ASSERT_EQ(set.bucket_count(), minimum_bucket_count); + + for(std::size_t next{}; next < minimum_bucket_count; ++next) { + ASSERT_TRUE(set.emplace(next).second); + ASSERT_LE(set.load_factor(), set.max_load_factor()); + } + + ASSERT_EQ(set.size(), minimum_bucket_count); + ASSERT_GT(set.bucket_count(), minimum_bucket_count); + ASSERT_TRUE(set.contains(minimum_bucket_count / 2u)); + ASSERT_EQ(set.bucket(minimum_bucket_count / 2u), minimum_bucket_count / 2u); + ASSERT_FALSE(set.contains(minimum_bucket_count)); + + ASSERT_TRUE(set.emplace(minimum_bucket_count).second); + + ASSERT_EQ(set.size(), minimum_bucket_count + 1u); + ASSERT_EQ(set.bucket_count(), minimum_bucket_count * 2u); + ASSERT_TRUE(set.contains(minimum_bucket_count / 2u)); + ASSERT_EQ(set.bucket(minimum_bucket_count / 2u), minimum_bucket_count / 2u); + ASSERT_TRUE(set.contains(minimum_bucket_count)); + + for(std::size_t next{}; next <= minimum_bucket_count; ++next) { + ASSERT_TRUE(set.contains(next)); + ASSERT_EQ(set.bucket(next), next); + } +} + +TEST(DenseSet, EmplaceSameBucket) { + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_set set; + + for(std::size_t next{}; next < minimum_bucket_count; ++next) { + ASSERT_EQ(set.cbegin(next), set.cend(next)); + } + + ASSERT_TRUE(set.emplace(1u).second); + ASSERT_TRUE(set.emplace(9u).second); + + ASSERT_EQ(set.size(), 2u); + ASSERT_TRUE(set.contains(1u)); + ASSERT_NE(set.find(9u), set.end()); + ASSERT_EQ(set.bucket(1u), 1u); + ASSERT_EQ(set.bucket(9u), 1u); + ASSERT_EQ(set.bucket_size(1u), 2u); + ASSERT_EQ(set.cbegin(6u), set.cend(6u)); +} + +TEST(DenseSet, Erase) { + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_set set; + + for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) { + set.emplace(next); + } + + ASSERT_EQ(set.bucket_count(), 2 * minimum_bucket_count); + ASSERT_EQ(set.size(), minimum_bucket_count + 1u); + + for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) { + ASSERT_TRUE(set.contains(next)); + } + + auto it = set.erase(++set.begin()); + it = set.erase(it, it + 1); + + ASSERT_EQ(*--set.end(), 6u); + ASSERT_EQ(set.erase(6u), 1u); + ASSERT_EQ(set.erase(6u), 0u); + + ASSERT_EQ(set.bucket_count(), 2 * minimum_bucket_count); + ASSERT_EQ(set.size(), minimum_bucket_count + 1u - 3u); + + ASSERT_EQ(it, ++set.begin()); + ASSERT_EQ(*it, 7u); + ASSERT_EQ(*--set.end(), 5u); + + for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) { + if(next == 1u || next == 8u || next == 6u) { + ASSERT_FALSE(set.contains(next)); + ASSERT_EQ(set.bucket_size(next), 0u); + } else { + ASSERT_TRUE(set.contains(next)); + ASSERT_EQ(set.bucket(next), next); + ASSERT_EQ(set.bucket_size(next), 1u); + } + } + + set.erase(set.begin(), set.end()); + + for(std::size_t next{}, last = minimum_bucket_count + 1u; next < last; ++next) { + ASSERT_FALSE(set.contains(next)); + } + + ASSERT_EQ(set.bucket_count(), 2 * minimum_bucket_count); + ASSERT_EQ(set.size(), 0u); +} + +TEST(DenseSet, EraseFromBucket) { + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_set set; + + ASSERT_EQ(set.bucket_count(), minimum_bucket_count); + ASSERT_EQ(set.size(), 0u); + + for(std::size_t next{}; next < 4u; ++next) { + ASSERT_TRUE(set.emplace(2u * minimum_bucket_count * next).second); + ASSERT_TRUE(set.emplace(2u * minimum_bucket_count * next + 2u).second); + ASSERT_TRUE(set.emplace(2u * minimum_bucket_count * (next + 1u) - 1u).second); + } + + ASSERT_EQ(set.bucket_count(), 2u * minimum_bucket_count); + ASSERT_EQ(set.size(), 12u); + + ASSERT_EQ(set.bucket_size(0u), 4u); + ASSERT_EQ(set.bucket_size(2u), 4u); + ASSERT_EQ(set.bucket_size(15u), 4u); + + set.erase(set.end() - 3, set.end()); + + ASSERT_EQ(set.bucket_count(), 2u * minimum_bucket_count); + ASSERT_EQ(set.size(), 9u); + + ASSERT_EQ(set.bucket_size(0u), 3u); + ASSERT_EQ(set.bucket_size(2u), 3u); + ASSERT_EQ(set.bucket_size(15u), 3u); + + for(std::size_t next{}; next < 3u; ++next) { + ASSERT_TRUE(set.contains(2u * minimum_bucket_count * next)); + ASSERT_EQ(set.bucket(2u * minimum_bucket_count * next), 0u); + + ASSERT_TRUE(set.contains(2u * minimum_bucket_count * next + 2u)); + ASSERT_EQ(set.bucket(2u * minimum_bucket_count * next + 2u), 2u); + + ASSERT_TRUE(set.contains(2u * minimum_bucket_count * (next + 1u) - 1u)); + ASSERT_EQ(set.bucket(2u * minimum_bucket_count * (next + 1u) - 1u), 15u); + } + + ASSERT_FALSE(set.contains(2u * minimum_bucket_count * 3u)); + ASSERT_FALSE(set.contains(2u * minimum_bucket_count * 3u + 2u)); + ASSERT_FALSE(set.contains(2u * minimum_bucket_count * (3u + 1u) - 1u)); + + set.erase(*++set.begin(0u)); + set.erase(*++set.begin(2u)); + set.erase(*++set.begin(15u)); + + ASSERT_EQ(set.bucket_count(), 2u * minimum_bucket_count); + ASSERT_EQ(set.size(), 6u); + + ASSERT_EQ(set.bucket_size(0u), 2u); + ASSERT_EQ(set.bucket_size(2u), 2u); + ASSERT_EQ(set.bucket_size(15u), 2u); + + ASSERT_FALSE(set.contains(2u * minimum_bucket_count * 1u)); + ASSERT_FALSE(set.contains(2u * minimum_bucket_count * 1u + 2u)); + ASSERT_FALSE(set.contains(2u * minimum_bucket_count * (1u + 1u) - 1u)); + + while(set.begin(15) != set.end(15u)) { + set.erase(*set.begin(15)); + } + + ASSERT_EQ(set.bucket_count(), 2u * minimum_bucket_count); + ASSERT_EQ(set.size(), 4u); + + ASSERT_EQ(set.bucket_size(0u), 2u); + ASSERT_EQ(set.bucket_size(2u), 2u); + ASSERT_EQ(set.bucket_size(15u), 0u); + + ASSERT_TRUE(set.contains(0u * minimum_bucket_count)); + ASSERT_TRUE(set.contains(0u * minimum_bucket_count + 2u)); + ASSERT_TRUE(set.contains(4u * minimum_bucket_count)); + ASSERT_TRUE(set.contains(4u * minimum_bucket_count + 2u)); + + set.erase(4u * minimum_bucket_count + 2u); + set.erase(0u * minimum_bucket_count); + + ASSERT_EQ(set.bucket_count(), 2u * minimum_bucket_count); + ASSERT_EQ(set.size(), 2u); + + ASSERT_EQ(set.bucket_size(0u), 1u); + ASSERT_EQ(set.bucket_size(2u), 1u); + ASSERT_EQ(set.bucket_size(15u), 0u); + + ASSERT_FALSE(set.contains(0u * minimum_bucket_count)); + ASSERT_TRUE(set.contains(0u * minimum_bucket_count + 2u)); + ASSERT_TRUE(set.contains(4u * minimum_bucket_count)); + ASSERT_FALSE(set.contains(4u * minimum_bucket_count + 2u)); +} + +TEST(DenseSet, Swap) { + entt::dense_set set; + entt::dense_set other; + + set.emplace(0); + + ASSERT_FALSE(set.empty()); + ASSERT_TRUE(other.empty()); + ASSERT_TRUE(set.contains(0)); + ASSERT_FALSE(other.contains(0)); + + set.swap(other); + + ASSERT_TRUE(set.empty()); + ASSERT_FALSE(other.empty()); + ASSERT_FALSE(set.contains(0)); + ASSERT_TRUE(other.contains(0)); +} + +TEST(DenseSet, LocalIterator) { + using iterator = typename entt::dense_set::local_iterator; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_set set; + set.emplace(3u); + set.emplace(3u + minimum_bucket_count); + + iterator end{set.begin(3u)}; + iterator begin{}; + begin = set.end(3u); + std::swap(begin, end); + + ASSERT_EQ(begin, set.begin(3u)); + ASSERT_EQ(end, set.end(3u)); + ASSERT_NE(begin, end); + + ASSERT_EQ(*begin.operator->(), 3u + minimum_bucket_count); + ASSERT_EQ(*begin, 3u + minimum_bucket_count); + + ASSERT_EQ(begin++, set.begin(3u)); + ASSERT_EQ(++begin, set.end(3u)); +} + +TEST(DenseSet, ConstLocalIterator) { + using iterator = typename entt::dense_set::const_local_iterator; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_set set; + set.emplace(3u); + set.emplace(3u + minimum_bucket_count); + + iterator cend{set.begin(3u)}; + iterator cbegin{}; + cbegin = set.end(3u); + std::swap(cbegin, cend); + + ASSERT_EQ(cbegin, set.begin(3u)); + ASSERT_EQ(cend, set.end(3u)); + ASSERT_NE(cbegin, cend); + + ASSERT_EQ(*cbegin.operator->(), 3u + minimum_bucket_count); + ASSERT_EQ(*cbegin, 3u + minimum_bucket_count); + + ASSERT_EQ(cbegin++, set.begin(3u)); + ASSERT_EQ(++cbegin, set.end(3u)); +} + +TEST(DenseSet, LocalIteratorConversion) { + entt::dense_set set; + set.emplace(3); + + typename entt::dense_set::local_iterator it = set.begin(set.bucket(3)); + typename entt::dense_set::const_local_iterator cit = it; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + ASSERT_EQ(*it, 3); + ASSERT_EQ(*it.operator->(), 3); + ASSERT_EQ(it.operator->(), cit.operator->()); + ASSERT_EQ(*it, *cit); + + ASSERT_EQ(it, cit); + ASSERT_NE(++cit, it); +} + +TEST(DenseSet, Rehash) { + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_set set; + set.emplace(32u); + + ASSERT_EQ(set.bucket_count(), minimum_bucket_count); + ASSERT_TRUE(set.contains(32u)); + ASSERT_EQ(set.bucket(32u), 0u); + + set.rehash(12u); + + ASSERT_EQ(set.bucket_count(), 2u * minimum_bucket_count); + ASSERT_TRUE(set.contains(32u)); + ASSERT_EQ(set.bucket(32u), 0u); + + set.rehash(44u); + + ASSERT_EQ(set.bucket_count(), 8u * minimum_bucket_count); + ASSERT_TRUE(set.contains(32u)); + ASSERT_EQ(set.bucket(32u), 32u); + + set.rehash(0u); + + ASSERT_EQ(set.bucket_count(), minimum_bucket_count); + ASSERT_TRUE(set.contains(32u)); + ASSERT_EQ(set.bucket(32u), 0u); + + for(std::size_t next{}; next < minimum_bucket_count; ++next) { + set.emplace(next); + } + + ASSERT_EQ(set.size(), minimum_bucket_count + 1u); + ASSERT_EQ(set.bucket_count(), 2u * minimum_bucket_count); + + set.rehash(0u); + + ASSERT_EQ(set.bucket_count(), 2u * minimum_bucket_count); + ASSERT_TRUE(set.contains(32u)); + + set.rehash(55u); + + ASSERT_EQ(set.bucket_count(), 8u * minimum_bucket_count); + ASSERT_TRUE(set.contains(32u)); + + set.rehash(2u); + + ASSERT_EQ(set.bucket_count(), 2u * minimum_bucket_count); + ASSERT_TRUE(set.contains(32u)); + ASSERT_EQ(set.bucket(32u), 0u); + + for(std::size_t next{}; next < minimum_bucket_count; ++next) { + ASSERT_TRUE(set.contains(next)); + ASSERT_EQ(set.bucket(next), next); + } + + ASSERT_EQ(set.bucket_size(0u), 2u); + ASSERT_EQ(set.bucket_size(3u), 1u); + + ASSERT_EQ(*set.begin(0u), 0u); + ASSERT_EQ(*++set.begin(0u), 32u); + + set.clear(); + set.rehash(2u); + + ASSERT_EQ(set.bucket_count(), minimum_bucket_count); + ASSERT_FALSE(set.contains(32u)); + + for(std::size_t next{}; next < minimum_bucket_count; ++next) { + ASSERT_FALSE(set.contains(next)); + } + + ASSERT_EQ(set.bucket_size(0u), 0u); + ASSERT_EQ(set.bucket_size(3u), 0u); +} + +TEST(DenseSet, Reserve) { + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_set set; + + ASSERT_EQ(set.bucket_count(), minimum_bucket_count); + + set.reserve(0u); + + ASSERT_EQ(set.bucket_count(), minimum_bucket_count); + + set.reserve(minimum_bucket_count); + + ASSERT_EQ(set.bucket_count(), 2 * minimum_bucket_count); + ASSERT_EQ(set.bucket_count(), entt::next_power_of_two(std::ceil(minimum_bucket_count / set.max_load_factor()))); +} + +TEST(DenseSet, ThrowingAllocator) { + using allocator = test::throwing_allocator; + using packed_allocator = test::throwing_allocator>; + using packed_exception = typename packed_allocator::exception_type; + + static constexpr std::size_t minimum_bucket_count = 8u; + entt::dense_set, std::equal_to, allocator> set{}; + + packed_allocator::trigger_on_allocate = true; + + ASSERT_EQ(set.bucket_count(), minimum_bucket_count); + ASSERT_THROW(set.reserve(2u * set.bucket_count()), packed_exception); + ASSERT_EQ(set.bucket_count(), minimum_bucket_count); + + packed_allocator::trigger_on_allocate = true; + + ASSERT_THROW(set.emplace(), packed_exception); + ASSERT_FALSE(set.contains(0u)); + + packed_allocator::trigger_on_allocate = true; + + ASSERT_THROW(set.emplace(std::size_t{}), packed_exception); + ASSERT_FALSE(set.contains(0u)); + + packed_allocator::trigger_on_allocate = true; + + ASSERT_THROW(set.insert(0u), packed_exception); + ASSERT_FALSE(set.contains(0u)); +} + +#if defined(ENTT_HAS_TRACKED_MEMORY_RESOURCE) + +TEST(DenseSet, NoUsesAllocatorConstruction) { + using allocator = std::pmr::polymorphic_allocator; + + test::tracked_memory_resource memory_resource{}; + entt::dense_set, std::equal_to, allocator> set{&memory_resource}; + + set.reserve(1u); + memory_resource.reset(); + set.emplace(0); + + ASSERT_TRUE(set.get_allocator().resource()->is_equal(memory_resource)); + ASSERT_EQ(memory_resource.do_allocate_counter(), 0u); + ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u); +} + +TEST(DenseSet, UsesAllocatorConstruction) { + using string_type = typename test::tracked_memory_resource::string_type; + using allocator = std::pmr::polymorphic_allocator; + + test::tracked_memory_resource memory_resource{}; + entt::dense_set, std::equal_to, allocator> set{&memory_resource}; + + set.reserve(1u); + memory_resource.reset(); + set.emplace(test::tracked_memory_resource::default_value); + + ASSERT_TRUE(set.get_allocator().resource()->is_equal(memory_resource)); + ASSERT_GT(memory_resource.do_allocate_counter(), 0u); + ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u); +} + +#endif diff --git a/modules/entt/test/entt/core/algorithm.cpp b/modules/entt/test/entt/core/algorithm.cpp index 34b55ae..625b729 100644 --- a/modules/entt/test/entt/core/algorithm.cpp +++ b/modules/entt/test/entt/core/algorithm.cpp @@ -1,7 +1,12 @@ #include +#include #include #include +struct boxed_int { + int value; +}; + TEST(Algorithm, StdSort) { // well, I'm pretty sure it works, it's std::sort!! std::array arr{{4, 1, 3, 2, 0}}; @@ -9,8 +14,22 @@ TEST(Algorithm, StdSort) { sort(arr.begin(), arr.end()); - for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) { - ASSERT_LT(arr[i], arr[i+1]); + for(auto i = 0u; i < (arr.size() - 1u); ++i) { + ASSERT_LT(arr[i], arr[i + 1u]); + } +} + +TEST(Algorithm, StdSortBoxedInt) { + // well, I'm pretty sure it works, it's std::sort!! + std::array arr{{{4}, {1}, {3}, {2}, {0}, {6}}}; + entt::std_sort sort; + + sort(arr.begin(), arr.end(), [](const auto &lhs, const auto &rhs) { + return lhs.value > rhs.value; + }); + + for(auto i = 0u; i < (arr.size() - 1u); ++i) { + ASSERT_GT(arr[i].value, arr[i + 1u].value); } } @@ -20,8 +39,21 @@ TEST(Algorithm, InsertionSort) { sort(arr.begin(), arr.end()); - for(typename decltype(arr)::size_type i = 0; i < (arr.size() - 1); ++i) { - ASSERT_LT(arr[i], arr[i+1]); + for(auto i = 0u; i < (arr.size() - 1u); ++i) { + ASSERT_LT(arr[i], arr[i + 1u]); + } +} + +TEST(Algorithm, InsertionSortBoxedInt) { + std::array arr{{{4}, {1}, {3}, {2}, {0}, {6}}}; + entt::insertion_sort sort; + + sort(arr.begin(), arr.end(), [](const auto &lhs, const auto &rhs) { + return lhs.value > rhs.value; + }); + + for(auto i = 0u; i < (arr.size() - 1u); ++i) { + ASSERT_GT(arr[i].value, arr[i + 1u].value); } } @@ -31,3 +63,36 @@ TEST(Algorithm, InsertionSortEmptyContainer) { // this should crash with asan enabled if we break the constraint sort(vec.begin(), vec.end()); } + +TEST(Algorithm, RadixSort) { + std::array arr{{4, 1, 3, 2, 0}}; + entt::radix_sort<8, 32> sort; + + sort(arr.begin(), arr.end(), [](const auto &value) { + return value; + }); + + for(auto i = 0u; i < (arr.size() - 1u); ++i) { + ASSERT_LT(arr[i], arr[i + 1u]); + } +} + +TEST(Algorithm, RadixSortBoxedInt) { + std::array arr{{{4}, {1}, {3}, {2}, {0}, {6}}}; + entt::radix_sort<2, 6> sort; + + sort(arr.rbegin(), arr.rend(), [](const auto &instance) { + return instance.value; + }); + + for(auto i = 0u; i < (arr.size() - 1u); ++i) { + ASSERT_GT(arr[i].value, arr[i + 1u].value); + } +} + +TEST(Algorithm, RadixSortEmptyContainer) { + std::vector vec{}; + entt::radix_sort<8, 32> sort; + // this should crash with asan enabled if we break the constraint + sort(vec.begin(), vec.end()); +} diff --git a/modules/entt/test/entt/core/any.cpp b/modules/entt/test/entt/core/any.cpp new file mode 100644 index 0000000..2282be6 --- /dev/null +++ b/modules/entt/test/entt/core/any.cpp @@ -0,0 +1,1465 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct empty { + ~empty() { + ++counter; + } + + inline static int counter = 0; +}; + +struct fat { + fat(double v1, double v2, double v3, double v4) + : value{v1, v2, v3, v4} {} + + ~fat() { + ++counter; + } + + bool operator==(const fat &other) const { + return std::equal(std::begin(value), std::end(value), std::begin(other.value), std::end(other.value)); + } + + inline static int counter{0}; + double value[4]; +}; + +struct not_comparable { + bool operator==(const not_comparable &) const = delete; +}; + +struct not_copyable { + not_copyable() + : payload{} {} + + not_copyable(const not_copyable &) = delete; + not_copyable(not_copyable &&) = default; + + not_copyable &operator=(const not_copyable &) = delete; + not_copyable &operator=(not_copyable &&) = default; + + double payload; +}; + +struct not_movable { + not_movable() = default; + not_movable(const not_movable &) = default; + not_movable(not_movable &&) = delete; + + not_movable &operator=(const not_movable &) = default; + not_movable &operator=(not_movable &&) = delete; + + double payload; +}; + +struct alignas(64u) over_aligned {}; + +struct Any: ::testing::Test { + void SetUp() override { + fat::counter = 0; + empty::counter = 0; + } +}; + +using AnyDeathTest = Any; + +TEST_F(Any, SBO) { + entt::any any{'c'}; + + ASSERT_TRUE(any); + ASSERT_TRUE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(any), 'c'); +} + +TEST_F(Any, NoSBO) { + fat instance{.1, .2, .3, .4}; + entt::any any{instance}; + + ASSERT_TRUE(any); + ASSERT_TRUE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(any), instance); +} + +TEST_F(Any, Empty) { + entt::any any{}; + + ASSERT_FALSE(any); + ASSERT_TRUE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(any.data(), nullptr); +} + +TEST_F(Any, SBOInPlaceTypeConstruction) { + entt::any any{std::in_place_type, 42}; + + ASSERT_TRUE(any); + ASSERT_TRUE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(any), 42); + + auto other = any.as_ref(); + + ASSERT_TRUE(other); + ASSERT_FALSE(other.owner()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(other), 42); + ASSERT_EQ(other.data(), any.data()); +} + +TEST_F(Any, SBOAsRefConstruction) { + int value = 42; + entt::any any{entt::forward_as_any(value)}; + + ASSERT_TRUE(any); + ASSERT_FALSE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(&any), &value); + ASSERT_EQ(entt::any_cast(&any), &value); + ASSERT_EQ(entt::any_cast(&std::as_const(any)), &value); + ASSERT_EQ(entt::any_cast(&std::as_const(any)), &value); + + ASSERT_EQ(entt::any_cast(any), 42); + ASSERT_EQ(entt::any_cast(any), 42); + + ASSERT_EQ(any.data(), &value); + ASSERT_EQ(std::as_const(any).data(), &value); + + any.emplace(value); + + ASSERT_TRUE(any); + ASSERT_FALSE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), &value); + + auto other = any.as_ref(); + + ASSERT_TRUE(other); + ASSERT_FALSE(other.owner()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(other), 42); + ASSERT_EQ(other.data(), any.data()); +} + +TEST_F(Any, SBOAsConstRefConstruction) { + const int value = 42; + entt::any any{entt::forward_as_any(value)}; + + ASSERT_TRUE(any); + ASSERT_FALSE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(&any), &value); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(&std::as_const(any)), &value); + ASSERT_EQ(entt::any_cast(&std::as_const(any)), &value); + + ASSERT_EQ(entt::any_cast(any), 42); + ASSERT_EQ(entt::any_cast(any), 42); + + ASSERT_EQ(any.data(), nullptr); + ASSERT_EQ(std::as_const(any).data(), &value); + + any.emplace(value); + + ASSERT_TRUE(any); + ASSERT_FALSE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), &value); + + auto other = any.as_ref(); + + ASSERT_TRUE(other); + ASSERT_FALSE(other.owner()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(other), 42); + ASSERT_EQ(other.data(), any.data()); +} + +TEST_F(Any, SBOCopyConstruction) { + entt::any any{42}; + entt::any other{any}; + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + ASSERT_TRUE(any.owner()); + ASSERT_TRUE(other.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&other), nullptr); + ASSERT_EQ(entt::any_cast(other), 42); +} + +TEST_F(Any, SBOCopyAssignment) { + entt::any any{42}; + entt::any other{3}; + + other = any; + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + ASSERT_TRUE(any.owner()); + ASSERT_TRUE(other.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&other), nullptr); + ASSERT_EQ(entt::any_cast(other), 42); +} + +TEST_F(Any, SBOMoveConstruction) { + entt::any any{42}; + entt::any other{std::move(any)}; + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + ASSERT_TRUE(any.owner()); + ASSERT_TRUE(other.owner()); + ASSERT_NE(any.data(), nullptr); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&other), nullptr); + ASSERT_EQ(entt::any_cast(other), 42); +} + +TEST_F(Any, SBOMoveAssignment) { + entt::any any{42}; + entt::any other{3}; + + other = std::move(any); + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + ASSERT_TRUE(any.owner()); + ASSERT_TRUE(other.owner()); + ASSERT_NE(any.data(), nullptr); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&other), nullptr); + ASSERT_EQ(entt::any_cast(other), 42); +} + +TEST_F(Any, SBODirectAssignment) { + entt::any any{}; + any = 42; + + ASSERT_TRUE(any); + ASSERT_TRUE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(any), 42); +} + +TEST_F(Any, SBOAssignValue) { + entt::any any{42}; + entt::any other{3}; + entt::any invalid{'c'}; + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), 42); + + ASSERT_TRUE(any.assign(other)); + ASSERT_FALSE(any.assign(invalid)); + ASSERT_EQ(entt::any_cast(any), 3); +} + +TEST_F(Any, SBOAsRefAssignValue) { + int value = 42; + entt::any any{entt::forward_as_any(value)}; + entt::any other{3}; + entt::any invalid{'c'}; + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), 42); + + ASSERT_TRUE(any.assign(other)); + ASSERT_FALSE(any.assign(invalid)); + ASSERT_EQ(entt::any_cast(any), 3); + ASSERT_EQ(value, 3); +} + +TEST_F(Any, SBOAsConstRefAssignValue) { + const int value = 42; + entt::any any{entt::forward_as_any(value)}; + entt::any other{3}; + entt::any invalid{'c'}; + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), 42); + + ASSERT_FALSE(any.assign(other)); + ASSERT_FALSE(any.assign(invalid)); + ASSERT_EQ(entt::any_cast(any), 42); + ASSERT_EQ(value, 42); +} + +TEST_F(Any, SBOTransferValue) { + entt::any any{42}; + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), 42); + + ASSERT_TRUE(any.assign(3)); + ASSERT_FALSE(any.assign('c')); + ASSERT_EQ(entt::any_cast(any), 3); +} + +TEST_F(Any, SBOTransferConstValue) { + const int value = 3; + entt::any any{42}; + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), 42); + + ASSERT_TRUE(any.assign(entt::forward_as_any(value))); + ASSERT_EQ(entt::any_cast(any), 3); +} + +TEST_F(Any, SBOAsRefTransferValue) { + int value = 42; + entt::any any{entt::forward_as_any(value)}; + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), 42); + + ASSERT_TRUE(any.assign(3)); + ASSERT_FALSE(any.assign('c')); + ASSERT_EQ(entt::any_cast(any), 3); + ASSERT_EQ(value, 3); +} + +TEST_F(Any, SBOAsConstRefTransferValue) { + const int value = 42; + entt::any any{entt::forward_as_any(value)}; + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), 42); + + ASSERT_FALSE(any.assign(3)); + ASSERT_FALSE(any.assign('c')); + ASSERT_EQ(entt::any_cast(any), 42); + ASSERT_EQ(value, 42); +} + +TEST_F(Any, NoSBOInPlaceTypeConstruction) { + fat instance{.1, .2, .3, .4}; + entt::any any{std::in_place_type, instance}; + + ASSERT_TRUE(any); + ASSERT_TRUE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(any), instance); + + auto other = any.as_ref(); + + ASSERT_TRUE(other); + ASSERT_FALSE(other.owner()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(other), (fat{.1, .2, .3, .4})); + ASSERT_EQ(other.data(), any.data()); +} + +TEST_F(Any, NoSBOAsRefConstruction) { + fat instance{.1, .2, .3, .4}; + entt::any any{entt::forward_as_any(instance)}; + + ASSERT_TRUE(any); + ASSERT_FALSE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(&any), &instance); + ASSERT_EQ(entt::any_cast(&any), &instance); + ASSERT_EQ(entt::any_cast(&std::as_const(any)), &instance); + ASSERT_EQ(entt::any_cast(&std::as_const(any)), &instance); + + ASSERT_EQ(entt::any_cast(any), instance); + ASSERT_EQ(entt::any_cast(any), instance); + + ASSERT_EQ(any.data(), &instance); + ASSERT_EQ(std::as_const(any).data(), &instance); + + any.emplace(instance); + + ASSERT_TRUE(any); + ASSERT_FALSE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), &instance); + + auto other = any.as_ref(); + + ASSERT_TRUE(other); + ASSERT_FALSE(other.owner()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(other), (fat{.1, .2, .3, .4})); + ASSERT_EQ(other.data(), any.data()); +} + +TEST_F(Any, NoSBOAsConstRefConstruction) { + const fat instance{.1, .2, .3, .4}; + entt::any any{entt::forward_as_any(instance)}; + + ASSERT_TRUE(any); + ASSERT_FALSE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(&any), &instance); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(&std::as_const(any)), &instance); + ASSERT_EQ(entt::any_cast(&std::as_const(any)), &instance); + + ASSERT_EQ(entt::any_cast(any), instance); + ASSERT_EQ(entt::any_cast(any), instance); + + ASSERT_EQ(any.data(), nullptr); + ASSERT_EQ(std::as_const(any).data(), &instance); + + any.emplace(instance); + + ASSERT_TRUE(any); + ASSERT_FALSE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), &instance); + + auto other = any.as_ref(); + + ASSERT_TRUE(other); + ASSERT_FALSE(other.owner()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(other), (fat{.1, .2, .3, .4})); + ASSERT_EQ(other.data(), any.data()); +} + +TEST_F(Any, NoSBOCopyConstruction) { + fat instance{.1, .2, .3, .4}; + entt::any any{instance}; + entt::any other{any}; + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + ASSERT_TRUE(any.owner()); + ASSERT_TRUE(other.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&other), nullptr); + ASSERT_EQ(entt::any_cast(other), instance); +} + +TEST_F(Any, NoSBOCopyAssignment) { + fat instance{.1, .2, .3, .4}; + entt::any any{instance}; + entt::any other{3}; + + other = any; + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + ASSERT_TRUE(any.owner()); + ASSERT_TRUE(other.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&other), nullptr); + ASSERT_EQ(entt::any_cast(other), instance); +} + +TEST_F(Any, NoSBOMoveConstruction) { + fat instance{.1, .2, .3, .4}; + entt::any any{instance}; + entt::any other{std::move(any)}; + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + ASSERT_TRUE(any.owner()); + ASSERT_TRUE(other.owner()); + ASSERT_EQ(any.data(), nullptr); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&other), nullptr); + ASSERT_EQ(entt::any_cast(other), instance); +} + +TEST_F(Any, NoSBOMoveAssignment) { + fat instance{.1, .2, .3, .4}; + entt::any any{instance}; + entt::any other{3}; + + other = std::move(any); + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + ASSERT_TRUE(any.owner()); + ASSERT_TRUE(other.owner()); + ASSERT_EQ(any.data(), nullptr); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&other), nullptr); + ASSERT_EQ(entt::any_cast(other), instance); +} + +TEST_F(Any, NoSBODirectAssignment) { + fat instance{.1, .2, .3, .4}; + entt::any any{}; + any = instance; + + ASSERT_TRUE(any); + ASSERT_TRUE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(any), instance); +} + +TEST_F(Any, NoSBOAssignValue) { + entt::any any{fat{.1, .2, .3, .4}}; + entt::any other{fat{.0, .1, .2, .3}}; + entt::any invalid{'c'}; + + const void *addr = std::as_const(any).data(); + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), (fat{.1, .2, .3, .4})); + + ASSERT_TRUE(any.assign(other)); + ASSERT_FALSE(any.assign(invalid)); + ASSERT_EQ(entt::any_cast(any), (fat{.0, .1, .2, .3})); + ASSERT_EQ(addr, std::as_const(any).data()); +} + +TEST_F(Any, NoSBOAsRefAssignValue) { + fat instance{.1, .2, .3, .4}; + entt::any any{entt::forward_as_any(instance)}; + entt::any other{fat{.0, .1, .2, .3}}; + entt::any invalid{'c'}; + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), (fat{.1, .2, .3, .4})); + + ASSERT_TRUE(any.assign(other)); + ASSERT_FALSE(any.assign(invalid)); + ASSERT_EQ(entt::any_cast(any), (fat{.0, .1, .2, .3})); + ASSERT_EQ(instance, (fat{.0, .1, .2, .3})); +} + +TEST_F(Any, NoSBOAsConstRefAssignValue) { + const fat instance{.1, .2, .3, .4}; + entt::any any{entt::forward_as_any(instance)}; + entt::any other{fat{.0, .1, .2, .3}}; + entt::any invalid{'c'}; + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), (fat{.1, .2, .3, .4})); + + ASSERT_FALSE(any.assign(other)); + ASSERT_FALSE(any.assign(invalid)); + ASSERT_EQ(entt::any_cast(any), (fat{.1, .2, .3, .4})); + ASSERT_EQ(instance, (fat{.1, .2, .3, .4})); +} + +TEST_F(Any, NoSBOTransferValue) { + entt::any any{fat{.1, .2, .3, .4}}; + + const void *addr = std::as_const(any).data(); + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), (fat{.1, .2, .3, .4})); + + ASSERT_TRUE(any.assign(fat{.0, .1, .2, .3})); + ASSERT_FALSE(any.assign('c')); + ASSERT_EQ(entt::any_cast(any), (fat{.0, .1, .2, .3})); + ASSERT_EQ(addr, std::as_const(any).data()); +} + +TEST_F(Any, NoSBOTransferConstValue) { + const fat instance{.0, .1, .2, .3}; + entt::any any{fat{.1, .2, .3, .4}}; + + const void *addr = std::as_const(any).data(); + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), (fat{.1, .2, .3, .4})); + + ASSERT_TRUE(any.assign(entt::forward_as_any(instance))); + ASSERT_EQ(entt::any_cast(any), (fat{.0, .1, .2, .3})); + ASSERT_EQ(addr, std::as_const(any).data()); +} + +TEST_F(Any, NoSBOAsRefTransferValue) { + fat instance{.1, .2, .3, .4}; + entt::any any{entt::forward_as_any(instance)}; + + const void *addr = std::as_const(any).data(); + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), (fat{.1, .2, .3, .4})); + + ASSERT_TRUE(any.assign(fat{.0, .1, .2, .3})); + ASSERT_FALSE(any.assign('c')); + ASSERT_EQ(entt::any_cast(any), (fat{.0, .1, .2, .3})); + ASSERT_EQ(instance, (fat{.0, .1, .2, .3})); + ASSERT_EQ(addr, std::as_const(any).data()); +} + +TEST_F(Any, NoSBOAsConstRefTransferValue) { + const fat instance{.1, .2, .3, .4}; + entt::any any{entt::forward_as_any(instance)}; + + const void *addr = std::as_const(any).data(); + + ASSERT_TRUE(any); + ASSERT_EQ(entt::any_cast(any), (fat{.1, .2, .3, .4})); + + ASSERT_FALSE(any.assign(fat{.0, .1, .2, .3})); + ASSERT_FALSE(any.assign('c')); + ASSERT_EQ(entt::any_cast(any), (fat{.1, .2, .3, .4})); + ASSERT_EQ(instance, (fat{.1, .2, .3, .4})); + ASSERT_EQ(addr, std::as_const(any).data()); +} + +TEST_F(Any, VoidInPlaceTypeConstruction) { + entt::any any{std::in_place_type}; + + ASSERT_FALSE(any); + ASSERT_TRUE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), nullptr); +} + +TEST_F(Any, VoidCopyConstruction) { + entt::any any{std::in_place_type}; + entt::any other{any}; + + ASSERT_FALSE(any); + ASSERT_FALSE(other); + ASSERT_TRUE(any.owner()); + ASSERT_TRUE(other.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(&other), nullptr); +} + +TEST_F(Any, VoidCopyAssignment) { + entt::any any{std::in_place_type}; + entt::any other{42}; + + other = any; + + ASSERT_FALSE(any); + ASSERT_FALSE(other); + ASSERT_TRUE(any.owner()); + ASSERT_TRUE(other.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(&other), nullptr); +} + +TEST_F(Any, VoidMoveConstruction) { + entt::any any{std::in_place_type}; + entt::any other{std::move(any)}; + + ASSERT_FALSE(any); + ASSERT_FALSE(other); + ASSERT_TRUE(any.owner()); + ASSERT_TRUE(other.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(&other), nullptr); +} + +TEST_F(Any, VoidMoveAssignment) { + entt::any any{std::in_place_type}; + entt::any other{42}; + + other = std::move(any); + + ASSERT_FALSE(any); + ASSERT_FALSE(other); + ASSERT_TRUE(any.owner()); + ASSERT_TRUE(other.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(&other), nullptr); +} + +TEST_F(Any, SBOMoveValidButUnspecifiedState) { + entt::any any{42}; + entt::any other{std::move(any)}; + entt::any valid = std::move(other); + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + ASSERT_TRUE(valid); +} + +TEST_F(Any, NoSBOMoveValidButUnspecifiedState) { + fat instance{.1, .2, .3, .4}; + entt::any any{instance}; + entt::any other{std::move(any)}; + entt::any valid = std::move(other); + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + ASSERT_TRUE(valid); +} + +TEST_F(Any, VoidMoveValidButUnspecifiedState) { + entt::any any{std::in_place_type}; + entt::any other{std::move(any)}; + entt::any valid = std::move(other); + + ASSERT_FALSE(any); + ASSERT_FALSE(other); + ASSERT_FALSE(valid); +} + +TEST_F(Any, SBODestruction) { + { + entt::any any{std::in_place_type}; + any.emplace(); + any = empty{}; + entt::any other{std::move(any)}; + any = std::move(other); + } + + ASSERT_EQ(empty::counter, 6); +} + +TEST_F(Any, NoSBODestruction) { + { + entt::any any{std::in_place_type, 1., 2., 3., 4.}; + any.emplace(1., 2., 3., 4.); + any = fat{1., 2., 3., 4.}; + entt::any other{std::move(any)}; + any = std::move(other); + } + + ASSERT_EQ(fat::counter, 4); +} + +TEST_F(Any, VoidDestruction) { + // just let asan tell us if everything is ok here + [[maybe_unused]] entt::any any{std::in_place_type}; +} + +TEST_F(Any, Emplace) { + entt::any any{}; + any.emplace(42); + + ASSERT_TRUE(any); + ASSERT_TRUE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(any), 42); +} + +TEST_F(Any, EmplaceVoid) { + entt::any any{}; + any.emplace(); + + ASSERT_FALSE(any); + ASSERT_TRUE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); +} + +TEST_F(Any, Reset) { + entt::any any{42}; + + ASSERT_TRUE(any); + ASSERT_TRUE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + + any.reset(); + + ASSERT_FALSE(any); + ASSERT_TRUE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + + int value = 42; + any.emplace(value); + + ASSERT_TRUE(any); + ASSERT_FALSE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); + + any.reset(); + + ASSERT_FALSE(any); + ASSERT_TRUE(any.owner()); + ASSERT_EQ(any.type(), entt::type_id()); +} + +TEST_F(Any, SBOSwap) { + entt::any lhs{'c'}; + entt::any rhs{42}; + + std::swap(lhs, rhs); + + ASSERT_TRUE(lhs.owner()); + ASSERT_TRUE(rhs.owner()); + + ASSERT_EQ(lhs.type(), entt::type_id()); + ASSERT_EQ(rhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(lhs), 42); + ASSERT_EQ(entt::any_cast(rhs), 'c'); +} + +TEST_F(Any, NoSBOSwap) { + entt::any lhs{fat{.1, .2, .3, .4}}; + entt::any rhs{fat{.4, .3, .2, .1}}; + + std::swap(lhs, rhs); + + ASSERT_TRUE(lhs.owner()); + ASSERT_TRUE(rhs.owner()); + + ASSERT_EQ(entt::any_cast(lhs), (fat{.4, .3, .2, .1})); + ASSERT_EQ(entt::any_cast(rhs), (fat{.1, .2, .3, .4})); +} + +TEST_F(Any, VoidSwap) { + entt::any lhs{std::in_place_type}; + entt::any rhs{std::in_place_type}; + const auto *pre = lhs.data(); + + std::swap(lhs, rhs); + + ASSERT_TRUE(lhs.owner()); + ASSERT_TRUE(rhs.owner()); + + ASSERT_EQ(pre, lhs.data()); +} + +TEST_F(Any, SBOWithNoSBOSwap) { + entt::any lhs{fat{.1, .2, .3, .4}}; + entt::any rhs{'c'}; + + std::swap(lhs, rhs); + + ASSERT_TRUE(lhs.owner()); + ASSERT_TRUE(rhs.owner()); + + ASSERT_EQ(lhs.type(), entt::type_id()); + ASSERT_EQ(rhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(lhs), 'c'); + ASSERT_EQ(entt::any_cast(rhs), (fat{.1, .2, .3, .4})); +} + +TEST_F(Any, SBOWithRefSwap) { + int value = 3; + entt::any lhs{entt::forward_as_any(value)}; + entt::any rhs{'c'}; + + std::swap(lhs, rhs); + + ASSERT_TRUE(lhs.owner()); + ASSERT_FALSE(rhs.owner()); + + ASSERT_EQ(lhs.type(), entt::type_id()); + ASSERT_EQ(rhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(lhs), 'c'); + ASSERT_EQ(entt::any_cast(rhs), 3); + ASSERT_EQ(rhs.data(), &value); +} + +TEST_F(Any, SBOWithConstRefSwap) { + const int value = 3; + entt::any lhs{entt::forward_as_any(value)}; + entt::any rhs{'c'}; + + std::swap(lhs, rhs); + + ASSERT_TRUE(lhs.owner()); + ASSERT_FALSE(rhs.owner()); + + ASSERT_EQ(lhs.type(), entt::type_id()); + ASSERT_EQ(rhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(lhs), 'c'); + ASSERT_EQ(entt::any_cast(rhs), 3); + ASSERT_EQ(rhs.data(), nullptr); + ASSERT_EQ(std::as_const(rhs).data(), &value); +} + +TEST_F(Any, SBOWithEmptySwap) { + entt::any lhs{'c'}; + entt::any rhs{}; + + std::swap(lhs, rhs); + + ASSERT_FALSE(lhs); + ASSERT_TRUE(lhs.owner()); + ASSERT_EQ(rhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(rhs), 'c'); + + std::swap(lhs, rhs); + + ASSERT_FALSE(rhs); + ASSERT_TRUE(rhs.owner()); + ASSERT_EQ(lhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(lhs), 'c'); +} + +TEST_F(Any, SBOWithVoidSwap) { + entt::any lhs{'c'}; + entt::any rhs{std::in_place_type}; + + std::swap(lhs, rhs); + + ASSERT_FALSE(lhs); + ASSERT_TRUE(lhs.owner()); + ASSERT_EQ(rhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(rhs), 'c'); + + std::swap(lhs, rhs); + + ASSERT_FALSE(rhs); + ASSERT_TRUE(rhs.owner()); + ASSERT_EQ(lhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(lhs), 'c'); +} + +TEST_F(Any, NoSBOWithRefSwap) { + int value = 3; + entt::any lhs{entt::forward_as_any(value)}; + entt::any rhs{fat{.1, .2, .3, .4}}; + + std::swap(lhs, rhs); + + ASSERT_TRUE(lhs.owner()); + ASSERT_FALSE(rhs.owner()); + + ASSERT_EQ(lhs.type(), entt::type_id()); + ASSERT_EQ(rhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(lhs), (fat{.1, .2, .3, .4})); + ASSERT_EQ(entt::any_cast(rhs), 3); + ASSERT_EQ(rhs.data(), &value); +} + +TEST_F(Any, NoSBOWithConstRefSwap) { + const int value = 3; + entt::any lhs{entt::forward_as_any(value)}; + entt::any rhs{fat{.1, .2, .3, .4}}; + + std::swap(lhs, rhs); + + ASSERT_TRUE(lhs.owner()); + ASSERT_FALSE(rhs.owner()); + + ASSERT_EQ(lhs.type(), entt::type_id()); + ASSERT_EQ(rhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(lhs), (fat{.1, .2, .3, .4})); + ASSERT_EQ(entt::any_cast(rhs), 3); + ASSERT_EQ(rhs.data(), nullptr); + ASSERT_EQ(std::as_const(rhs).data(), &value); +} + +TEST_F(Any, NoSBOWithEmptySwap) { + entt::any lhs{fat{.1, .2, .3, .4}}; + entt::any rhs{}; + + std::swap(lhs, rhs); + + ASSERT_FALSE(lhs); + ASSERT_TRUE(lhs.owner()); + ASSERT_EQ(rhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(rhs), (fat{.1, .2, .3, .4})); + + std::swap(lhs, rhs); + + ASSERT_FALSE(rhs); + ASSERT_TRUE(rhs.owner()); + ASSERT_EQ(lhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(lhs), (fat{.1, .2, .3, .4})); +} + +TEST_F(Any, NoSBOWithVoidSwap) { + entt::any lhs{fat{.1, .2, .3, .4}}; + entt::any rhs{std::in_place_type}; + + std::swap(lhs, rhs); + + ASSERT_FALSE(lhs); + ASSERT_TRUE(lhs.owner()); + ASSERT_EQ(rhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(rhs), (fat{.1, .2, .3, .4})); + + std::swap(lhs, rhs); + + ASSERT_FALSE(rhs); + ASSERT_TRUE(rhs.owner()); + ASSERT_EQ(lhs.type(), entt::type_id()); + ASSERT_EQ(entt::any_cast(&lhs), nullptr); + ASSERT_EQ(entt::any_cast(&rhs), nullptr); + ASSERT_EQ(entt::any_cast(lhs), (fat{.1, .2, .3, .4})); +} + +TEST_F(Any, AsRef) { + entt::any any{42}; + auto ref = any.as_ref(); + auto cref = std::as_const(any).as_ref(); + + ASSERT_FALSE(ref.owner()); + ASSERT_FALSE(cref.owner()); + + ASSERT_EQ(entt::any_cast(&any), any.data()); + ASSERT_EQ(entt::any_cast(&ref), any.data()); + ASSERT_EQ(entt::any_cast(&cref), nullptr); + + ASSERT_EQ(entt::any_cast(&any), any.data()); + ASSERT_EQ(entt::any_cast(&ref), any.data()); + ASSERT_EQ(entt::any_cast(&cref), any.data()); + + ASSERT_EQ(entt::any_cast(any), 42); + ASSERT_EQ(entt::any_cast(ref), 42); + ASSERT_EQ(entt::any_cast(cref), 42); + + ASSERT_EQ(entt::any_cast(any), 42); + ASSERT_EQ(entt::any_cast(ref), 42); + ASSERT_EQ(entt::any_cast(cref), 42); + + ASSERT_EQ(entt::any_cast(any), 42); + ASSERT_EQ(entt::any_cast(any), 42); + ASSERT_EQ(entt::any_cast(ref), 42); + ASSERT_EQ(entt::any_cast(ref), 42); + ASSERT_EQ(entt::any_cast(&cref), nullptr); + ASSERT_EQ(entt::any_cast(cref), 42); + + entt::any_cast(any) = 3; + + ASSERT_EQ(entt::any_cast(any), 3); + ASSERT_EQ(entt::any_cast(ref), 3); + ASSERT_EQ(entt::any_cast(cref), 3); + + std::swap(ref, cref); + + ASSERT_FALSE(ref.owner()); + ASSERT_FALSE(cref.owner()); + + ASSERT_EQ(entt::any_cast(&ref), nullptr); + ASSERT_EQ(entt::any_cast(&cref), any.data()); + + ref = ref.as_ref(); + cref = std::as_const(cref).as_ref(); + + ASSERT_FALSE(ref.owner()); + ASSERT_FALSE(cref.owner()); + + ASSERT_EQ(entt::any_cast(&ref), nullptr); + ASSERT_EQ(entt::any_cast(&cref), nullptr); + ASSERT_EQ(entt::any_cast(&ref), any.data()); + ASSERT_EQ(entt::any_cast(&cref), any.data()); + + ASSERT_EQ(entt::any_cast(&ref), nullptr); + ASSERT_EQ(entt::any_cast(&cref), nullptr); + + ASSERT_EQ(entt::any_cast(ref), 3); + ASSERT_EQ(entt::any_cast(cref), 3); + + ref = 42; + cref = 42; + + ASSERT_TRUE(ref.owner()); + ASSERT_TRUE(cref.owner()); + + ASSERT_NE(entt::any_cast(&ref), nullptr); + ASSERT_NE(entt::any_cast(&cref), nullptr); + ASSERT_EQ(entt::any_cast(ref), 42); + ASSERT_EQ(entt::any_cast(cref), 42); + ASSERT_EQ(entt::any_cast(ref), 42); + ASSERT_EQ(entt::any_cast(cref), 42); + ASSERT_NE(entt::any_cast(&ref), any.data()); + ASSERT_NE(entt::any_cast(&cref), any.data()); +} + +TEST_F(Any, Comparable) { + auto test = [](entt::any any, entt::any other) { + ASSERT_EQ(any, any); + ASSERT_NE(other, any); + ASSERT_NE(any, entt::any{}); + + ASSERT_TRUE(any == any); + ASSERT_FALSE(other == any); + ASSERT_TRUE(any != other); + ASSERT_TRUE(entt::any{} != any); + }; + + int value = 42; + + test('c', 'a'); + test(fat{.1, .2, .3, .4}, fat{.0, .1, .2, .3}); + test(entt::forward_as_any(value), 3); + test(3, entt::make_any(value)); + test('c', value); +} + +TEST_F(Any, NotComparable) { + auto test = [](const auto &instance) { + auto any = entt::forward_as_any(instance); + + ASSERT_EQ(any, any); + ASSERT_NE(any, entt::any{instance}); + ASSERT_NE(entt::any{}, any); + + ASSERT_TRUE(any == any); + ASSERT_FALSE(any == entt::any{instance}); + ASSERT_TRUE(entt::any{} != any); + }; + + test(not_comparable{}); + test(std::unordered_map{}); + test(std::vector{}); +} + +TEST_F(Any, CompareVoid) { + entt::any any{std::in_place_type}; + + ASSERT_EQ(any, any); + ASSERT_EQ(any, entt::any{std::in_place_type}); + ASSERT_NE(entt::any{'a'}, any); + ASSERT_EQ(any, entt::any{}); + + ASSERT_TRUE(any == any); + ASSERT_TRUE(any == entt::any{std::in_place_type}); + ASSERT_FALSE(entt::any{'a'} == any); + ASSERT_TRUE(any != entt::any{'a'}); + ASSERT_FALSE(entt::any{} != any); +} + +TEST_F(Any, AnyCast) { + entt::any any{42}; + const auto &cany = any; + + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(&cany), nullptr); + ASSERT_EQ(*entt::any_cast(&any), 42); + ASSERT_EQ(*entt::any_cast(&cany), 42); + ASSERT_EQ(entt::any_cast(any), 42); + ASSERT_EQ(entt::any_cast(cany), 42); + + not_copyable instance{}; + instance.payload = 42.; + entt::any ref{entt::forward_as_any(instance)}; + entt::any cref{entt::forward_as_any(std::as_const(instance).payload)}; + + ASSERT_EQ(entt::any_cast(std::move(ref)).payload, 42.); + ASSERT_EQ(entt::any_cast(std::move(cref)), 42.); + ASSERT_EQ(entt::any_cast(entt::any{42}), 42); +} + +TEST_F(AnyDeathTest, AnyCast) { + entt::any any{42}; + const auto &cany = any; + + ASSERT_DEATH(entt::any_cast(any), ""); + ASSERT_DEATH(entt::any_cast(cany), ""); + + not_copyable instance{}; + instance.payload = 42.; + entt::any ref{entt::forward_as_any(instance)}; + entt::any cref{entt::forward_as_any(std::as_const(instance).payload)}; + + ASSERT_DEATH(entt::any_cast(std::as_const(ref).as_ref()), ""); + ASSERT_DEATH(entt::any_cast(entt::any{42}), ""); +} + +TEST_F(Any, MakeAny) { + int value = 42; + auto any = entt::make_any(value); + auto ext = entt::make_any(value); + auto ref = entt::make_any(value); + + ASSERT_TRUE(any); + ASSERT_TRUE(ext); + ASSERT_TRUE(ref); + + ASSERT_TRUE(any.owner()); + ASSERT_TRUE(ext.owner()); + ASSERT_FALSE(ref.owner()); + + ASSERT_EQ(entt::any_cast(any), 42); + ASSERT_EQ(entt::any_cast(ext), 42); + ASSERT_EQ(entt::any_cast(ref), 42); + + ASSERT_EQ(decltype(any)::length, entt::any::length); + ASSERT_NE(decltype(ext)::length, entt::any::length); + ASSERT_EQ(decltype(ref)::length, entt::any::length); + + ASSERT_NE(any.data(), &value); + ASSERT_NE(ext.data(), &value); + ASSERT_EQ(ref.data(), &value); +} + +TEST_F(Any, ForwardAsAny) { + int value = 42; + auto any = entt::forward_as_any(std::move(value)); + auto ref = entt::forward_as_any(value); + auto cref = entt::forward_as_any(std::as_const(value)); + + ASSERT_TRUE(any); + ASSERT_TRUE(ref); + ASSERT_TRUE(cref); + + ASSERT_TRUE(any.owner()); + ASSERT_FALSE(ref.owner()); + ASSERT_FALSE(cref.owner()); + + ASSERT_NE(entt::any_cast(&any), nullptr); + ASSERT_NE(entt::any_cast(&ref), nullptr); + ASSERT_EQ(entt::any_cast(&cref), nullptr); + + ASSERT_EQ(entt::any_cast(any), 42); + ASSERT_EQ(entt::any_cast(ref), 42); + ASSERT_EQ(entt::any_cast(cref), 42); + + ASSERT_NE(any.data(), &value); + ASSERT_EQ(ref.data(), &value); +} + +TEST_F(Any, NotCopyableType) { + const not_copyable value{}; + entt::any any{std::in_place_type}; + entt::any other = entt::forward_as_any(value); + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + + ASSERT_TRUE(any.owner()); + ASSERT_FALSE(other.owner()); + ASSERT_EQ(any.type(), other.type()); + + ASSERT_FALSE(any.assign(other)); + ASSERT_FALSE(any.assign(std::move(other))); + + entt::any copy{any}; + + ASSERT_TRUE(any); + ASSERT_FALSE(copy); + + ASSERT_TRUE(any.owner()); + ASSERT_TRUE(copy.owner()); + + copy = any; + + ASSERT_TRUE(any); + ASSERT_FALSE(copy); + + ASSERT_TRUE(any.owner()); + ASSERT_TRUE(copy.owner()); +} + +TEST_F(Any, NotCopyableValueType) { + std::vector vec{}; + vec.emplace_back(std::in_place_type); + vec.shrink_to_fit(); + + ASSERT_EQ(vec.size(), 1u); + ASSERT_EQ(vec.capacity(), 1u); + ASSERT_TRUE(vec[0u]); + + // strong exception guarantee due to noexcept move ctor + vec.emplace_back(std::in_place_type); + + ASSERT_EQ(vec.size(), 2u); + ASSERT_TRUE(vec[0u]); + ASSERT_TRUE(vec[1u]); +} + +TEST_F(Any, NotMovableType) { + entt::any any{std::in_place_type}; + entt::any other{std::in_place_type}; + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + + ASSERT_TRUE(any.owner()); + ASSERT_TRUE(other.owner()); + ASSERT_EQ(any.type(), other.type()); + + ASSERT_TRUE(any.assign(other)); + ASSERT_TRUE(any.assign(std::move(other))); + + entt::any copy{any}; + + ASSERT_TRUE(any); + ASSERT_TRUE(copy); + + ASSERT_TRUE(any.owner()); + ASSERT_TRUE(copy.owner()); + + copy = any; + + ASSERT_TRUE(any); + ASSERT_TRUE(copy); + + ASSERT_TRUE(any.owner()); + ASSERT_TRUE(copy.owner()); +} + +TEST_F(Any, Array) { + entt::any any{std::in_place_type}; + entt::any copy{any}; + + ASSERT_TRUE(any); + ASSERT_FALSE(copy); + + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_NE(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(&any), nullptr); + ASSERT_EQ(entt::any_cast(&any), nullptr); + + entt::any_cast(any)[0] = 42; + + ASSERT_EQ(entt::any_cast(std::as_const(any))[0], 42); +} + +TEST_F(Any, CopyMoveReference) { + int value{}; + + auto test = [&](auto &&ref) { + value = 3; + + auto any = entt::forward_as_any(ref); + entt::any move = std::move(any); + entt::any copy = move; + + ASSERT_TRUE(any); + ASSERT_TRUE(move); + ASSERT_TRUE(copy); + + ASSERT_FALSE(any.owner()); + ASSERT_FALSE(move.owner()); + ASSERT_TRUE(copy.owner()); + + ASSERT_EQ(move.type(), entt::type_id()); + ASSERT_EQ(copy.type(), entt::type_id()); + + ASSERT_EQ(std::as_const(move).data(), &value); + ASSERT_NE(std::as_const(copy).data(), &value); + + ASSERT_EQ(entt::any_cast(move), 3); + ASSERT_EQ(entt::any_cast(copy), 3); + + value = 42; + + ASSERT_EQ(entt::any_cast(move), 42); + ASSERT_EQ(entt::any_cast(copy), 3); + }; + + test(value); + test(std::as_const(value)); +} + +TEST_F(Any, SBOVsZeroedSBOSize) { + entt::any sbo{42}; + const auto *broken = sbo.data(); + entt::any other = std::move(sbo); + + ASSERT_NE(broken, other.data()); + + entt::basic_any<0u> dyn{42}; + const auto *valid = dyn.data(); + entt::basic_any<0u> same = std::move(dyn); + + ASSERT_EQ(valid, same.data()); +} + +TEST_F(Any, SboAlignment) { + static constexpr auto alignment = alignof(over_aligned); + entt::basic_any sbo[2] = {over_aligned{}, over_aligned{}}; + const auto *data = sbo[0].data(); + + ASSERT_TRUE((reinterpret_cast(sbo[0u].data()) % alignment) == 0u); + ASSERT_TRUE((reinterpret_cast(sbo[1u].data()) % alignment) == 0u); + + std::swap(sbo[0], sbo[1]); + + ASSERT_TRUE((reinterpret_cast(sbo[0u].data()) % alignment) == 0u); + ASSERT_TRUE((reinterpret_cast(sbo[1u].data()) % alignment) == 0u); + + ASSERT_NE(data, sbo[1].data()); +} + +TEST_F(Any, NoSboAlignment) { + static constexpr auto alignment = alignof(over_aligned); + entt::basic_any nosbo[2] = {over_aligned{}, over_aligned{}}; + const auto *data = nosbo[0].data(); + + ASSERT_TRUE((reinterpret_cast(nosbo[0u].data()) % alignment) == 0u); + ASSERT_TRUE((reinterpret_cast(nosbo[1u].data()) % alignment) == 0u); + + std::swap(nosbo[0], nosbo[1]); + + ASSERT_TRUE((reinterpret_cast(nosbo[0u].data()) % alignment) == 0u); + ASSERT_TRUE((reinterpret_cast(nosbo[1u].data()) % alignment) == 0u); + + ASSERT_EQ(data, nosbo[1].data()); +} + +TEST_F(Any, AggregatesMustWork) { + struct aggregate_type { + int value; + }; + + // the goal of this test is to enforce the requirements for aggregate types + entt::any{std::in_place_type, 42}.emplace(42); +} + +TEST_F(Any, DeducedArrayType) { + entt::any any{"array of char"}; + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ((std::strcmp("array of char", entt::any_cast(any))), 0); + + any = "another array of char"; + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::type_id()); + ASSERT_EQ((std::strcmp("another array of char", entt::any_cast(any))), 0); +} diff --git a/modules/entt/test/entt/core/compressed_pair.cpp b/modules/entt/test/entt/core/compressed_pair.cpp new file mode 100644 index 0000000..f796bac --- /dev/null +++ b/modules/entt/test/entt/core/compressed_pair.cpp @@ -0,0 +1,183 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +struct empty_type {}; + +struct move_only_type { + move_only_type() + : value{new int{99}} {} + + move_only_type(int v) + : value{new int{v}} {} + + ~move_only_type() { + delete value; + } + + move_only_type(const move_only_type &) = delete; + move_only_type &operator=(const move_only_type &) = delete; + + move_only_type(move_only_type &&other) ENTT_NOEXCEPT + : value{std::exchange(other.value, nullptr)} {} + + move_only_type &operator=(move_only_type &&other) ENTT_NOEXCEPT { + delete value; + value = std::exchange(other.value, nullptr); + return *this; + } + + int *value; +}; + +struct non_default_constructible { + non_default_constructible(int v) + : value{v} {} + + int value; +}; + +TEST(CompressedPair, Size) { + struct local { + int value; + empty_type empty; + }; + + static_assert(sizeof(entt::compressed_pair) == sizeof(int[2u])); + static_assert(sizeof(entt::compressed_pair) == sizeof(int)); + static_assert(sizeof(entt::compressed_pair) == sizeof(int)); + static_assert(sizeof(entt::compressed_pair) < sizeof(local)); + static_assert(sizeof(entt::compressed_pair) < sizeof(std::pair)); +} + +TEST(CompressedPair, ConstructCopyMove) { + static_assert(!std::is_default_constructible_v>); + static_assert(std::is_default_constructible_v>); + + static_assert(std::is_copy_constructible_v>); + static_assert(!std::is_copy_constructible_v>); + static_assert(std::is_copy_assignable_v>); + static_assert(!std::is_copy_assignable_v>); + + static_assert(std::is_move_constructible_v>); + static_assert(std::is_move_assignable_v>); + + entt::compressed_pair copyable{non_default_constructible{42}, empty_type{}}; + auto by_copy{copyable}; + + ASSERT_EQ(by_copy.first().value, 42); + + by_copy.first().value = 3; + copyable = by_copy; + + ASSERT_EQ(copyable.first().value, 3); + + entt::compressed_pair movable{}; + auto by_move{std::move(movable)}; + + ASSERT_EQ(*by_move.second().value, 99); + ASSERT_EQ(movable.second().value, nullptr); + + *by_move.second().value = 3; + movable = std::move(by_move); + + ASSERT_EQ(*movable.second().value, 3); + ASSERT_EQ(by_move.second().value, nullptr); +} + +TEST(CompressedPair, PiecewiseConstruct) { + std::vector vec{42}; + entt::compressed_pair empty{std::piecewise_construct, std::make_tuple(), std::make_tuple()}; + entt::compressed_pair, std::size_t> pair{std::piecewise_construct, std::forward_as_tuple(std::move(vec)), std::make_tuple(sizeof(empty))}; + + ASSERT_EQ(pair.first().size(), 1u); + ASSERT_EQ(pair.second(), sizeof(empty)); + ASSERT_EQ(vec.size(), 0u); +} + +TEST(CompressedPair, DeductionGuide) { + int value = 42; + empty_type empty{}; + entt::compressed_pair pair{value, 3}; + + static_assert(std::is_same_v>); + + ASSERT_TRUE((std::is_same_v>)); + ASSERT_EQ(pair.first(), 42); + ASSERT_EQ(pair.second(), 3); +} + +TEST(CompressedPair, Getters) { + entt::compressed_pair pair{3, empty_type{}}; + const auto &cpair = pair; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + ASSERT_EQ(pair.first(), cpair.first()); + ASSERT_EQ(&pair.second(), &cpair.second()); +} + +TEST(CompressedPair, Swap) { + entt::compressed_pair pair{1, 2}; + entt::compressed_pair other{3, 4}; + + swap(pair, other); + + ASSERT_EQ(pair.first(), 3); + ASSERT_EQ(pair.second(), 4); + ASSERT_EQ(other.first(), 1); + ASSERT_EQ(other.second(), 2); + + pair.swap(other); + + ASSERT_EQ(pair.first(), 1); + ASSERT_EQ(pair.second(), 2); + ASSERT_EQ(other.first(), 3); + ASSERT_EQ(other.second(), 4); +} + +TEST(CompressedPair, Get) { + entt::compressed_pair pair{1, 2}; + + ASSERT_EQ(pair.get<0>(), 1); + ASSERT_EQ(pair.get<1>(), 2); + + ASSERT_EQ(&pair.get<0>(), &pair.first()); + ASSERT_EQ(&pair.get<1>(), &pair.second()); + + auto &&[first, second] = pair; + + ASSERT_EQ(first, 1); + ASSERT_EQ(second, 2); + + first = 3; + second = 4; + + ASSERT_EQ(pair.first(), 3); + ASSERT_EQ(pair.second(), 4); + + auto &[cfirst, csecond] = std::as_const(pair); + + ASSERT_EQ(cfirst, 3); + ASSERT_EQ(csecond, 4); + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + auto [tfirst, tsecond] = entt::compressed_pair{9, 99}; + + ASSERT_EQ(tfirst, 9); + ASSERT_EQ(tsecond, 99); + + static_assert(std::is_same_v); + static_assert(std::is_same_v); +} diff --git a/modules/entt/test/entt/core/enum.cpp b/modules/entt/test/entt/core/enum.cpp new file mode 100644 index 0000000..5d5d281 --- /dev/null +++ b/modules/entt/test/entt/core/enum.cpp @@ -0,0 +1,72 @@ +#include +#include +#include +#include + +enum class detected { + foo = 0x01, + bar = 0x02, + quux = 0x04, + _entt_enum_as_bitmask +}; + +// small type on purpose +enum class registered : std::uint8_t { + foo = 0x01, + bar = 0x02, + quux = 0x04 +}; + +template<> +struct entt::enum_as_bitmask + : std::true_type {}; + +template +struct Enum: testing::Test { + using type = Type; +}; + +using EnumTypes = ::testing::Types; + +TYPED_TEST_SUITE(Enum, EnumTypes, ); + +TYPED_TEST(Enum, Functionalities) { + using enum_type = typename TestFixture::type; + + ASSERT_TRUE(!!((enum_type::foo | enum_type::bar) & enum_type::foo)); + ASSERT_TRUE(!!((enum_type::foo | enum_type::bar) & enum_type::bar)); + ASSERT_TRUE(!((enum_type::foo | enum_type::bar) & enum_type::quux)); + + ASSERT_TRUE(!!((enum_type::foo ^ enum_type::bar) & enum_type::foo)); + ASSERT_TRUE(!((enum_type::foo ^ enum_type::foo) & enum_type::foo)); + + ASSERT_TRUE(!(~enum_type::foo & enum_type::foo)); + ASSERT_TRUE(!!(~enum_type::foo & enum_type::bar)); + + ASSERT_TRUE(enum_type::foo == enum_type::foo); + ASSERT_TRUE(enum_type::foo != enum_type::bar); + + enum_type value = enum_type::foo; + + ASSERT_TRUE(!!(value & enum_type::foo)); + ASSERT_TRUE(!(value & enum_type::bar)); + ASSERT_TRUE(!(value & enum_type::quux)); + + value |= (enum_type::bar | enum_type::quux); + + ASSERT_TRUE(!!(value & enum_type::foo)); + ASSERT_TRUE(!!(value & enum_type::bar)); + ASSERT_TRUE(!!(value & enum_type::quux)); + + value &= (enum_type::bar | enum_type::quux); + + ASSERT_TRUE(!(value & enum_type::foo)); + ASSERT_TRUE(!!(value & enum_type::bar)); + ASSERT_TRUE(!!(value & enum_type::quux)); + + value ^= enum_type::bar; + + ASSERT_TRUE(!(value & enum_type::foo)); + ASSERT_TRUE(!(value & enum_type::bar)); + ASSERT_TRUE(!!(value & enum_type::quux)); +} diff --git a/modules/entt/test/entt/core/family.cpp b/modules/entt/test/entt/core/family.cpp index 147ca82..83106af 100644 --- a/modules/entt/test/entt/core/family.cpp +++ b/modules/entt/test/entt/core/family.cpp @@ -16,7 +16,7 @@ TEST(Family, Functionalities) { } TEST(Family, Uniqueness) { - ASSERT_EQ(a_family::type, a_family::type); - ASSERT_EQ(a_family::type, a_family::type); - ASSERT_EQ(a_family::type, a_family::type); + ASSERT_NE(a_family::type, a_family::type); + ASSERT_NE(a_family::type, a_family::type); + ASSERT_NE(a_family::type, a_family::type); } diff --git a/modules/entt/test/entt/core/hashed_string.cpp b/modules/entt/test/entt/core/hashed_string.cpp index 37c1958..a1f2033 100644 --- a/modules/entt/test/entt/core/hashed_string.cpp +++ b/modules/entt/test/entt/core/hashed_string.cpp @@ -1,10 +1,32 @@ +#include #include #include #include #include #include +template +struct foobar_t; + +template<> +struct foobar_t { + static constexpr auto value = 0xbf9cf968; +}; + +template<> +struct foobar_t { + static constexpr auto value = 0x85944171f73967e8; +}; + +inline constexpr auto foobar_v = foobar_t::value; + +TEST(BasicHashedString, DeductionGuide) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); +} + TEST(HashedString, Functionalities) { + using namespace entt::literals; using hash_type = entt::hashed_string::hash_type; const char *bar = "bar"; @@ -17,17 +39,29 @@ TEST(HashedString, Functionalities) { ASSERT_STREQ(static_cast(bar_hs), bar); ASSERT_STREQ(foo_hs.data(), "foo"); ASSERT_STREQ(bar_hs.data(), bar); + ASSERT_EQ(foo_hs.size(), 3u); + ASSERT_EQ(bar_hs.size(), 3u); ASSERT_EQ(foo_hs, foo_hs); ASSERT_NE(foo_hs, bar_hs); entt::hashed_string hs{"foobar"}; - ASSERT_EQ(static_cast(hs), 0xbf9cf968); - ASSERT_EQ(hs.value(), 0xbf9cf968); + ASSERT_EQ(static_cast(hs), foobar_v); + ASSERT_EQ(hs.value(), foobar_v); ASSERT_EQ(foo_hs, "foo"_hs); ASSERT_NE(bar_hs, "foo"_hs); + + entt::hashed_string empty_hs{}; + + ASSERT_EQ(empty_hs, entt::hashed_string{}); + ASSERT_NE(empty_hs, foo_hs); + + empty_hs = foo_hs; + + ASSERT_NE(empty_hs, entt::hashed_string{}); + ASSERT_EQ(empty_hs, foo_hs); } TEST(HashedString, Empty) { @@ -35,30 +69,156 @@ TEST(HashedString, Empty) { entt::hashed_string hs{}; + ASSERT_EQ(hs.size(), 0u); ASSERT_EQ(static_cast(hs), hash_type{}); ASSERT_EQ(static_cast(hs), nullptr); } +TEST(HashedString, Correctness) { + const char *foobar = "foobar"; + std::string_view view{"foobar__", 6}; + + ASSERT_EQ(entt::hashed_string{foobar}, foobar_v); + ASSERT_EQ((entt::hashed_string{view.data(), view.size()}), foobar_v); + ASSERT_EQ(entt::hashed_string{"foobar"}, foobar_v); + + ASSERT_EQ(entt::hashed_string::value(foobar), foobar_v); + ASSERT_EQ(entt::hashed_string::value(view.data(), view.size()), foobar_v); + ASSERT_EQ(entt::hashed_string::value("foobar"), foobar_v); + + ASSERT_EQ(entt::hashed_string{foobar}.size(), 6u); + ASSERT_EQ((entt::hashed_string{view.data(), view.size()}).size(), 6u); + ASSERT_EQ(entt::hashed_string{"foobar"}.size(), 6u); +} + +TEST(HashedString, Order) { + using namespace entt::literals; + const entt::hashed_string lhs = "foo"_hs; + const entt::hashed_string rhs = "bar"_hs; + + ASSERT_FALSE(lhs < lhs); + ASSERT_FALSE(rhs < rhs); + + ASSERT_LT(rhs, lhs); + ASSERT_LE(rhs, lhs); + + ASSERT_GT(lhs, rhs); + ASSERT_GE(lhs, rhs); +} + TEST(HashedString, Constexprness) { - using hash_type = entt::hashed_string::hash_type; - // how would you test a constexpr otherwise? - (void)std::integral_constant{}; - (void)std::integral_constant{}; - ASSERT_TRUE(true); + using namespace entt::literals; + constexpr std::string_view view{"foobar__", 6}; + + static_assert(entt::hashed_string{"quux"} == "quux"_hs); + static_assert(entt::hashed_string{"foobar"} == foobar_v); + + static_assert(entt::hashed_string::value("quux") == "quux"_hs); + static_assert(entt::hashed_string::value("foobar") == foobar_v); + + static_assert(entt::hashed_string{"quux", 4} == "quux"_hs); + static_assert(entt::hashed_string{view.data(), view.size()} == foobar_v); + + static_assert(entt::hashed_string::value("quux", 4) == "quux"_hs); + static_assert(entt::hashed_string::value(view.data(), view.size()) == foobar_v); + + static_assert(entt::hashed_string{"bar"} < "foo"_hs); + static_assert(entt::hashed_string{"bar"} <= "bar"_hs); + + static_assert(entt::hashed_string{"foo"} > "bar"_hs); + static_assert(entt::hashed_string{"foo"} >= "foo"_hs); } -TEST(HashedString, ToValue) { - using hash_type = entt::hashed_string::hash_type; +TEST(HashedWString, Functionalities) { + using namespace entt::literals; + using hash_type = entt::hashed_wstring::hash_type; - const char *foobar = "foobar"; + const wchar_t *bar = L"bar"; + + auto foo_hws = entt::hashed_wstring{L"foo"}; + auto bar_hws = entt::hashed_wstring{bar}; + + ASSERT_NE(static_cast(foo_hws), static_cast(bar_hws)); + ASSERT_STREQ(static_cast(foo_hws), L"foo"); + ASSERT_STREQ(static_cast(bar_hws), bar); + ASSERT_STREQ(foo_hws.data(), L"foo"); + ASSERT_STREQ(bar_hws.data(), bar); + ASSERT_EQ(foo_hws.size(), 3u); + ASSERT_EQ(bar_hws.size(), 3u); + + ASSERT_EQ(foo_hws, foo_hws); + ASSERT_NE(foo_hws, bar_hws); + + entt::hashed_wstring hws{L"foobar"}; + + ASSERT_EQ(static_cast(hws), foobar_v); + ASSERT_EQ(hws.value(), foobar_v); - ASSERT_EQ(entt::hashed_string::to_value(foobar), 0xbf9cf968); - // how would you test a constexpr otherwise? - (void)std::integral_constant{}; + ASSERT_EQ(foo_hws, L"foo"_hws); + ASSERT_NE(bar_hws, L"foo"_hws); } -TEST(HashedString, StringView) { - std::string str{"__foobar__"}; - std::string_view view{str.data()+2, 6}; - ASSERT_EQ(entt::hashed_string::to_value(view.data(), view.size()), 0xbf9cf968); +TEST(HashedWString, Empty) { + using hash_type = entt::hashed_wstring::hash_type; + + entt::hashed_wstring hws{}; + + ASSERT_EQ(hws.size(), 0u); + ASSERT_EQ(static_cast(hws), hash_type{}); + ASSERT_EQ(static_cast(hws), nullptr); +} + +TEST(HashedWString, Correctness) { + const wchar_t *foobar = L"foobar"; + std::wstring_view view{L"foobar__", 6}; + + ASSERT_EQ(entt::hashed_wstring{foobar}, foobar_v); + ASSERT_EQ((entt::hashed_wstring{view.data(), view.size()}), foobar_v); + ASSERT_EQ(entt::hashed_wstring{L"foobar"}, foobar_v); + + ASSERT_EQ(entt::hashed_wstring::value(foobar), foobar_v); + ASSERT_EQ(entt::hashed_wstring::value(view.data(), view.size()), foobar_v); + ASSERT_EQ(entt::hashed_wstring::value(L"foobar"), foobar_v); + + ASSERT_EQ(entt::hashed_wstring{foobar}.size(), 6u); + ASSERT_EQ((entt::hashed_wstring{view.data(), view.size()}).size(), 6u); + ASSERT_EQ(entt::hashed_wstring{L"foobar"}.size(), 6u); +} + +TEST(HashedWString, Order) { + using namespace entt::literals; + const entt::hashed_wstring lhs = L"foo"_hws; + const entt::hashed_wstring rhs = L"bar"_hws; + + ASSERT_FALSE(lhs < lhs); + ASSERT_FALSE(rhs < rhs); + + ASSERT_LT(rhs, lhs); + ASSERT_LE(rhs, lhs); + + ASSERT_GT(lhs, rhs); + ASSERT_GE(lhs, rhs); +} + +TEST(HashedWString, Constexprness) { + using namespace entt::literals; + constexpr std::wstring_view view{L"foobar__", 6}; + + static_assert(entt::hashed_wstring{L"quux"} == L"quux"_hws); + static_assert(entt::hashed_wstring{L"foobar"} == foobar_v); + + static_assert(entt::hashed_wstring::value(L"quux") == L"quux"_hws); + static_assert(entt::hashed_wstring::value(L"foobar") == foobar_v); + + static_assert(entt::hashed_wstring{L"quux", 4} == L"quux"_hws); + static_assert(entt::hashed_wstring{view.data(), view.size()} == foobar_v); + + static_assert(entt::hashed_wstring::value(L"quux", 4) == L"quux"_hws); + static_assert(entt::hashed_wstring::value(view.data(), view.size()) == foobar_v); + + static_assert(entt::hashed_wstring{L"bar"} < L"foo"_hws); + static_assert(entt::hashed_wstring{L"bar"} <= L"bar"_hws); + + static_assert(entt::hashed_wstring{L"foo"} > L"bar"_hws); + static_assert(entt::hashed_wstring{L"foo"} >= L"foo"_hws); } diff --git a/modules/entt/test/entt/core/ident.cpp b/modules/entt/test/entt/core/ident.cpp index d70b84c..79eea1b 100644 --- a/modules/entt/test/entt/core/ident.cpp +++ b/modules/entt/test/entt/core/ident.cpp @@ -27,6 +27,5 @@ TEST(Identifier, Uniqueness) { TEST(Identifier, SingleType) { using id = entt::identifier; - std::integral_constant> ic; - (void)ic; + [[maybe_unused]] std::integral_constant> ic; } diff --git a/modules/entt/test/entt/core/iterator.cpp b/modules/entt/test/entt/core/iterator.cpp new file mode 100644 index 0000000..7f6d489 --- /dev/null +++ b/modules/entt/test/entt/core/iterator.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include + +struct clazz { + int value{0}; +}; + +TEST(InputIteratorPointer, Functionalities) { + static_assert(!std::is_default_constructible_v>); + static_assert(!std::is_copy_constructible_v>); + static_assert(std::is_move_constructible_v>); + static_assert(!std::is_copy_assignable_v>); + static_assert(std::is_move_assignable_v>); + + clazz instance{}; + entt::input_iterator_pointer ptr{std::move(instance)}; + ptr->value = 42; + + ASSERT_EQ(instance.value, 0); + ASSERT_EQ(ptr->value, 42); +} + +TEST(IterableAdaptor, Functionalities) { + std::vector vec{1, 2}; + entt::iterable_adaptor iterable{vec.begin(), vec.end()}; + decltype(iterable) other{}; + + ASSERT_NO_THROW(other = iterable); + ASSERT_NO_THROW(std::swap(other, iterable)); + + ASSERT_EQ(iterable.begin(), vec.begin()); + ASSERT_EQ(iterable.end(), vec.end()); + + ASSERT_EQ(*iterable.cbegin(), 1); + ASSERT_EQ(*++iterable.cbegin(), 2); + ASSERT_EQ(++iterable.cbegin(), --iterable.end()); + + for(auto value: entt::iterable_adaptor{vec.data(), vec.data() + 1u}) { + ASSERT_EQ(value, 1); + } +} diff --git a/modules/entt/test/entt/core/memory.cpp b/modules/entt/test/entt/core/memory.cpp new file mode 100644 index 0000000..e5e7bc6 --- /dev/null +++ b/modules/entt/test/entt/core/memory.cpp @@ -0,0 +1,234 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../common/basic_test_allocator.hpp" +#include "../common/throwing_allocator.hpp" +#include "../common/throwing_type.hpp" +#include "../common/tracked_memory_resource.hpp" + +TEST(ToAddress, Functionalities) { + std::shared_ptr shared = std::make_shared(); + auto *plain = std::addressof(*shared); + + ASSERT_EQ(entt::to_address(shared), plain); + ASSERT_EQ(entt::to_address(plain), plain); +} + +TEST(PoccaPocmaAndPocs, Functionalities) { + test::basic_test_allocator lhs, rhs; + // honestly, I don't even know how one is supposed to test such a thing :) + entt::propagate_on_container_copy_assignment(lhs, rhs); + entt::propagate_on_container_move_assignment(lhs, rhs); + entt::propagate_on_container_swap(lhs, rhs); +} + +TEST(IsPowerOfTwo, Functionalities) { + // constexpr-ness guaranteed + constexpr auto zero_is_power_of_two = entt::is_power_of_two(0u); + + ASSERT_FALSE(zero_is_power_of_two); + ASSERT_TRUE(entt::is_power_of_two(1u)); + ASSERT_TRUE(entt::is_power_of_two(2u)); + ASSERT_TRUE(entt::is_power_of_two(4u)); + ASSERT_FALSE(entt::is_power_of_two(7u)); + ASSERT_TRUE(entt::is_power_of_two(128u)); + ASSERT_FALSE(entt::is_power_of_two(200u)); +} + +TEST(NextPowerOfTwo, Functionalities) { + // constexpr-ness guaranteed + constexpr auto next_power_of_two_of_zero = entt::next_power_of_two(0u); + + ASSERT_EQ(next_power_of_two_of_zero, 1u); + ASSERT_EQ(entt::next_power_of_two(1u), 1u); + ASSERT_EQ(entt::next_power_of_two(2u), 2u); + ASSERT_EQ(entt::next_power_of_two(3u), 4u); + ASSERT_EQ(entt::next_power_of_two(17u), 32u); + ASSERT_EQ(entt::next_power_of_two(32u), 32u); + ASSERT_EQ(entt::next_power_of_two(33u), 64u); + ASSERT_EQ(entt::next_power_of_two(std::pow(2, 16)), std::pow(2, 16)); + ASSERT_EQ(entt::next_power_of_two(std::pow(2, 16) + 1u), std::pow(2, 17)); +} + +TEST(NextPowerOfTwoDeathTest, Functionalities) { + ASSERT_DEATH(static_cast(entt::next_power_of_two((std::size_t{1u} << (std::numeric_limits::digits - 1)) + 1)), ""); +} + +TEST(FastMod, Functionalities) { + // constexpr-ness guaranteed + constexpr auto fast_mod_of_zero = entt::fast_mod(0u, 8u); + + ASSERT_EQ(fast_mod_of_zero, 0u); + ASSERT_EQ(entt::fast_mod(7u, 8u), 7u); + ASSERT_EQ(entt::fast_mod(8u, 8u), 0u); +} + +TEST(AllocateUnique, Functionalities) { + test::throwing_allocator allocator{}; + test::throwing_allocator::trigger_on_allocate = true; + test::throwing_type::trigger_on_value = 0; + + ASSERT_THROW((entt::allocate_unique(allocator, 0)), test::throwing_allocator::exception_type); + ASSERT_THROW((entt::allocate_unique(allocator, test::throwing_type{0})), test::throwing_type::exception_type); + + std::unique_ptr>> ptr = entt::allocate_unique(allocator, 42); + + ASSERT_TRUE(ptr); + ASSERT_EQ(*ptr, 42); + + ptr.reset(); + + ASSERT_FALSE(ptr); +} + +#if defined(ENTT_HAS_TRACKED_MEMORY_RESOURCE) + +TEST(AllocateUnique, NoUsesAllocatorConstruction) { + test::tracked_memory_resource memory_resource{}; + std::pmr::polymorphic_allocator allocator{&memory_resource}; + + using type = std::unique_ptr>>; + type ptr = entt::allocate_unique(allocator, 0); + + ASSERT_EQ(memory_resource.do_allocate_counter(), 1u); + ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u); +} + +TEST(AllocateUnique, UsesAllocatorConstruction) { + using string_type = typename test::tracked_memory_resource::string_type; + + test::tracked_memory_resource memory_resource{}; + std::pmr::polymorphic_allocator allocator{&memory_resource}; + + using type = std::unique_ptr>>; + type ptr = entt::allocate_unique(allocator, test::tracked_memory_resource::default_value); + + ASSERT_GT(memory_resource.do_allocate_counter(), 1u); + ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u); +} + +#endif + +TEST(UsesAllocatorConstructionArgs, NoUsesAllocatorConstruction) { + const auto value = 42; + const auto args = entt::uses_allocator_construction_args(std::allocator{}, value); + + static_assert(std::tuple_size_v == 1u); + static_assert(std::is_same_v>); + + ASSERT_EQ(std::get<0>(args), value); +} + +TEST(UsesAllocatorConstructionArgs, LeadingAllocatorConvention) { + const auto value = 42; + const auto args = entt::uses_allocator_construction_args>(std::allocator{}, value, 'c'); + + static_assert(std::tuple_size_v == 4u); + static_assert(std::is_same_v &, const int &, char &&>>); + + ASSERT_EQ(std::get<2>(args), value); +} + +TEST(UsesAllocatorConstructionArgs, TrailingAllocatorConvention) { + const auto size = 42u; + const auto args = entt::uses_allocator_construction_args>(std::allocator{}, size); + + static_assert(std::tuple_size_v == 2u); + static_assert(std::is_same_v &>>); + + ASSERT_EQ(std::get<0>(args), size); +} + +TEST(UsesAllocatorConstructionArgs, PairPiecewiseConstruct) { + const auto size = 42u; + const auto tup = std::make_tuple(size); + const auto args = entt::uses_allocator_construction_args>>(std::allocator{}, std::piecewise_construct, std::make_tuple(3), tup); + + static_assert(std::tuple_size_v == 3u); + static_assert(std::is_same_v, std::tuple &>>>); + + ASSERT_EQ(std::get<0>(std::get<2>(args)), size); +} + +TEST(UsesAllocatorConstructionArgs, PairNoArgs) { + [[maybe_unused]] const auto args = entt::uses_allocator_construction_args>>(std::allocator{}); + + static_assert(std::tuple_size_v == 3u); + static_assert(std::is_same_v, std::tuple &>>>); +} + +TEST(UsesAllocatorConstructionArgs, PairValues) { + const auto size = 42u; + const auto args = entt::uses_allocator_construction_args>>(std::allocator{}, 3, size); + + static_assert(std::tuple_size_v == 3u); + static_assert(std::is_same_v, std::tuple &>>>); + + ASSERT_EQ(std::get<0>(std::get<2>(args)), size); +} + +TEST(UsesAllocatorConstructionArgs, PairConstLValueReference) { + const auto value = std::make_pair(3, 42u); + const auto args = entt::uses_allocator_construction_args>>(std::allocator{}, value); + + static_assert(std::tuple_size_v == 3u); + static_assert(std::is_same_v, std::tuple &>>>); + + ASSERT_EQ(std::get<0>(std::get<1>(args)), 3); + ASSERT_EQ(std::get<0>(std::get<2>(args)), 42u); +} + +TEST(UsesAllocatorConstructionArgs, PairRValueReference) { + [[maybe_unused]] const auto args = entt::uses_allocator_construction_args>>(std::allocator{}, std::make_pair(3, 42u)); + + static_assert(std::tuple_size_v == 3u); + static_assert(std::is_same_v, std::tuple &>>>); +} + +TEST(MakeObjUsingAllocator, Functionalities) { + const auto size = 42u; + test::throwing_allocator::trigger_on_allocate = true; + + ASSERT_THROW((entt::make_obj_using_allocator>>(test::throwing_allocator{}, size)), test::throwing_allocator::exception_type); + + const auto vec = entt::make_obj_using_allocator>(std::allocator{}, size); + + ASSERT_FALSE(vec.empty()); + ASSERT_EQ(vec.size(), size); +} + +TEST(UninitializedConstructUsingAllocator, NoUsesAllocatorConstruction) { + std::aligned_storage_t storage; + std::allocator allocator{}; + + int *value = entt::uninitialized_construct_using_allocator(reinterpret_cast(&storage), allocator, 42); + + ASSERT_EQ(*value, 42); +} + +#if defined(ENTT_HAS_TRACKED_MEMORY_RESOURCE) + +TEST(UninitializedConstructUsingAllocator, UsesAllocatorConstruction) { + using string_type = typename test::tracked_memory_resource::string_type; + + test::tracked_memory_resource memory_resource{}; + std::pmr::polymorphic_allocator allocator{&memory_resource}; + std::aligned_storage_t storage; + + string_type *value = entt::uninitialized_construct_using_allocator(reinterpret_cast(&storage), allocator, test::tracked_memory_resource::default_value); + + ASSERT_GT(memory_resource.do_allocate_counter(), 0u); + ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u); + ASSERT_EQ(*value, test::tracked_memory_resource::default_value); + + value->~string_type(); +} + +#endif diff --git a/modules/entt/test/entt/core/monostate.cpp b/modules/entt/test/entt/core/monostate.cpp index 639c7f2..4675050 100644 --- a/modules/entt/test/entt/core/monostate.cpp +++ b/modules/entt/test/entt/core/monostate.cpp @@ -3,6 +3,8 @@ #include TEST(Monostate, Functionalities) { + using namespace entt::literals; + const bool b_pre = entt::monostate{}; const int i_pre = entt::monostate<"foobar"_hs>{}; diff --git a/modules/entt/test/entt/core/tuple.cpp b/modules/entt/test/entt/core/tuple.cpp new file mode 100644 index 0000000..b5e907a --- /dev/null +++ b/modules/entt/test/entt/core/tuple.cpp @@ -0,0 +1,25 @@ +#include +#include +#include + +TEST(Tuple, UnwrapTuple) { + auto single = std::make_tuple(42); + auto multi = std::make_tuple(42, 'c'); + auto ref = std::forward_as_tuple(std::get<0>(single)); + + ASSERT_TRUE((std::is_same_v)); + ASSERT_TRUE((std::is_same_v &>)); + ASSERT_TRUE((std::is_same_v)); + + ASSERT_TRUE((std::is_same_v)); + ASSERT_TRUE((std::is_same_v &&>)); + ASSERT_TRUE((std::is_same_v)); + + ASSERT_TRUE((std::is_same_v)); + ASSERT_TRUE((std::is_same_v &>)); + ASSERT_TRUE((std::is_same_v)); + + ASSERT_EQ(entt::unwrap_tuple(single), 42); + ASSERT_EQ(entt::unwrap_tuple(multi), multi); + ASSERT_EQ(entt::unwrap_tuple(std::move(ref)), 42); +} diff --git a/modules/entt/test/entt/core/type_info.cpp b/modules/entt/test/entt/core/type_info.cpp new file mode 100644 index 0000000..aca2e22 --- /dev/null +++ b/modules/entt/test/entt/core/type_info.cpp @@ -0,0 +1,117 @@ +#include +#include +#include +#include +#include +#include +#include + +template<> +struct entt::type_name final { + [[nodiscard]] static constexpr std::string_view value() ENTT_NOEXCEPT { + return std::string_view{""}; + } +}; + +TEST(TypeIndex, Functionalities) { + ASSERT_EQ(entt::type_index::value(), entt::type_index::value()); + ASSERT_NE(entt::type_index::value(), entt::type_index::value()); + ASSERT_NE(entt::type_index::value(), entt::type_index::value()); + ASSERT_NE(entt::type_index::value(), entt::type_index::value()); + ASSERT_EQ(static_cast(entt::type_index{}), entt::type_index::value()); +} + +TEST(TypeHash, Functionalities) { + ASSERT_NE(entt::type_hash::value(), entt::type_hash::value()); + ASSERT_NE(entt::type_hash::value(), entt::type_hash::value()); + ASSERT_EQ(entt::type_hash::value(), entt::type_hash::value()); + ASSERT_EQ(static_cast(entt::type_hash{}), entt::type_hash::value()); +} + +TEST(TypeName, Functionalities) { + ASSERT_EQ(entt::type_name::value(), std::string_view{"int"}); + ASSERT_EQ(entt::type_name{}.value(), std::string_view{""}); + + ASSERT_TRUE((entt::type_name>::value() == std::string_view{"std::integral_constant"}) + || (entt::type_name>::value() == std::string_view{"std::__1::integral_constant"}) + || (entt::type_name>::value() == std::string_view{"struct std::integral_constant"})); + + ASSERT_TRUE(((entt::type_name, double>>::value()) == std::string_view{"entt::type_list, double>"}) + || ((entt::type_name, double>>::value()) == std::string_view{"struct entt::type_list,double>"})); + + ASSERT_EQ(static_cast(entt::type_name{}), entt::type_name::value()); +} + +TEST(TypeInfo, Functionalities) { + static_assert(std::is_copy_constructible_v); + static_assert(std::is_move_constructible_v); + static_assert(std::is_copy_assignable_v); + static_assert(std::is_move_assignable_v); + + entt::type_info info{std::in_place_type}; + entt::type_info other{std::in_place_type}; + + ASSERT_EQ(info, entt::type_info{std::in_place_type}); + ASSERT_EQ(info, entt::type_info{std::in_place_type}); + ASSERT_EQ(info, entt::type_info{std::in_place_type}); + + ASSERT_NE(info, other); + ASSERT_TRUE(info == info); + ASSERT_FALSE(info != info); + + ASSERT_EQ(info.index(), entt::type_index::value()); + ASSERT_EQ(info.hash(), entt::type_hash::value()); + ASSERT_EQ(info.name(), entt::type_name::value()); + + other = info; + + ASSERT_EQ(other.index(), entt::type_index::value()); + ASSERT_EQ(other.hash(), entt::type_hash::value()); + ASSERT_EQ(other.name(), entt::type_name::value()); + + ASSERT_EQ(other.index(), info.index()); + ASSERT_EQ(other.hash(), info.hash()); + ASSERT_EQ(other.name(), info.name()); + + other = std::move(info); + + ASSERT_EQ(other.index(), entt::type_index::value()); + ASSERT_EQ(other.hash(), entt::type_hash::value()); + ASSERT_EQ(other.name(), entt::type_name::value()); + + ASSERT_EQ(other.index(), info.index()); + ASSERT_EQ(other.hash(), info.hash()); + ASSERT_EQ(other.name(), info.name()); +} + +TEST(TypeInfo, Order) { + entt::type_info rhs = entt::type_id(); + entt::type_info lhs = entt::type_id(); + + // let's adjust the two objects since values are generated at runtime + rhs < lhs ? void() : std::swap(lhs, rhs); + + ASSERT_FALSE(lhs < lhs); + ASSERT_FALSE(rhs < rhs); + + ASSERT_LT(rhs, lhs); + ASSERT_LE(rhs, lhs); + + ASSERT_GT(lhs, rhs); + ASSERT_GE(lhs, rhs); +} + +TEST(TypeId, Functionalities) { + const int value = 42; + + ASSERT_EQ(entt::type_id(value), entt::type_id()); + ASSERT_EQ(entt::type_id(42), entt::type_id()); + + ASSERT_EQ(entt::type_id(), entt::type_id()); + ASSERT_EQ(entt::type_id(), entt::type_id()); + ASSERT_EQ(entt::type_id(), entt::type_id()); + ASSERT_NE(entt::type_id(), entt::type_id()); + + ASSERT_EQ(&entt::type_id(), &entt::type_id()); + ASSERT_NE(&entt::type_id(), &entt::type_id()); +} diff --git a/modules/entt/test/entt/core/type_traits.cpp b/modules/entt/test/entt/core/type_traits.cpp index cd1653c..e85497a 100644 --- a/modules/entt/test/entt/core/type_traits.cpp +++ b/modules/entt/test/entt/core/type_traits.cpp @@ -1,14 +1,197 @@ +#include +#include +#include +#include +#include +#include #include +#include #include +struct not_comparable { + bool operator==(const not_comparable &) const = delete; +}; + +struct nlohmann_json_like final { + using value_type = nlohmann_json_like; + + bool operator==(const nlohmann_json_like &) const { + return true; + } +}; + +TEST(SizeOf, Functionalities) { + static_assert(entt::size_of_v == 0u); + static_assert(entt::size_of_v == sizeof(char)); + static_assert(entt::size_of_v == 0u); + static_assert(entt::size_of_v == sizeof(int[3])); +} + +TEST(UnpackAsType, Functionalities) { + auto test = [](auto &&...args) { + return [](entt::unpack_as_type... value) { + return (value + ... + 0); + }; + }; + + ASSERT_EQ(test('c', 42., true)(1, 2, 3), 6); +} + +TEST(UnpackAsValue, Functionalities) { + auto test = [](auto &&...args) { + return (entt::unpack_as_value<2, decltype(args)> + ... + 0); + }; + + ASSERT_EQ(test('c', 42., true), 6); +} + +TEST(IntegralConstant, Functionalities) { + entt::integral_constant<3> constant{}; + + static_assert(std::is_same_v::value_type, int>); + static_assert(constant.value == 3); +} + +TEST(Choice, Functionalities) { + static_assert(std::is_base_of_v, entt::choice_t<1>>); + static_assert(!std::is_base_of_v, entt::choice_t<0>>); +} + TEST(TypeList, Functionalities) { using type = entt::type_list; using other = entt::type_list; - ASSERT_EQ((type::size), (decltype(type::size){2})); - ASSERT_EQ((other::size), (decltype(other::size){1})); - ASSERT_TRUE((std::is_same_v, entt::type_list>)); - ASSERT_TRUE((std::is_same_v, entt::type_list>)); - ASSERT_TRUE((std::is_same_v, entt::type_list>)); - ASSERT_TRUE((std::is_same_v>, entt::type_list>)); + static_assert(type::size == 2u); + static_assert(other::size == 1u); + + static_assert(std::is_same_v>); + static_assert(std::is_same_v, entt::type_list>); + static_assert(std::is_same_v, entt::type_list>); + static_assert(std::is_same_v, entt::type_list>); + static_assert(std::is_same_v>, entt::type_list>); + + static_assert(entt::type_list_contains_v); + static_assert(entt::type_list_contains_v); + static_assert(!entt::type_list_contains_v); + + static_assert(std::is_same_v, int>); + static_assert(std::is_same_v, char>); + static_assert(std::is_same_v, double>); + + static_assert(std::is_same_v, entt::type_list>, entt::type_list>); + static_assert(std::is_same_v, entt::type_list>, entt::type_list<>>); + static_assert(std::is_same_v, entt::type_list>, entt::type_list>); + static_assert(std::is_same_v, entt::type_list>, entt::type_list>); + static_assert(std::is_same_v, entt::type_list>, entt::type_list>); +} + +TEST(ValueList, Functionalities) { + using value = entt::value_list<0, 2>; + using other = entt::value_list<1>; + + static_assert(value::size == 2u); + static_assert(other::size == 1u); + + static_assert(std::is_same_v>); + static_assert(std::is_same_v, entt::value_list<0, 2, 1, 0, 2, 1>>); + static_assert(std::is_same_v, entt::value_list<0, 2, 1>>); + static_assert(std::is_same_v, entt::value_list<0, 2, 0, 2>>); + + static_assert(entt::value_list_element_v<0u, value> == 0); + static_assert(entt::value_list_element_v<1u, value> == 2); + static_assert(entt::value_list_element_v<0u, other> == 1); +} + +TEST(IsApplicable, Functionalities) { + static_assert(entt::is_applicable_v>); + static_assert(!entt::is_applicable_v>); + + static_assert(entt::is_applicable_r_v>); + static_assert(!entt::is_applicable_r_v>); + static_assert(!entt::is_applicable_r_v>); +} + +TEST(IsComplete, Functionalities) { + static_assert(!entt::is_complete_v); + static_assert(entt::is_complete_v); +} + +TEST(IsIterator, Functionalities) { + static_assert(!entt::is_iterator_v); + static_assert(!entt::is_iterator_v); + + static_assert(!entt::is_iterator_v); + static_assert(entt::is_iterator_v); + + static_assert(entt::is_iterator_v::iterator>); + static_assert(entt::is_iterator_v::const_iterator>); + static_assert(entt::is_iterator_v::reverse_iterator>); +} + +TEST(IsEBCOEligible, Functionalities) { + static_assert(entt::is_ebco_eligible_v); + static_assert(!entt::is_ebco_eligible_v); + static_assert(!entt::is_ebco_eligible_v); + static_assert(!entt::is_ebco_eligible_v); +} + +TEST(IsTransparent, Functionalities) { + static_assert(!entt::is_transparent_v>); + static_assert(entt::is_transparent_v>); + static_assert(!entt::is_transparent_v>); + static_assert(entt::is_transparent_v>); +} + +TEST(IsEqualityComparable, Functionalities) { + static_assert(entt::is_equality_comparable_v); + static_assert(entt::is_equality_comparable_v); + static_assert(entt::is_equality_comparable_v>); + static_assert(entt::is_equality_comparable_v>>); + static_assert(entt::is_equality_comparable_v>); + static_assert(entt::is_equality_comparable_v>>); + static_assert(entt::is_equality_comparable_v>); + static_assert(entt::is_equality_comparable_v>>); + static_assert(entt::is_equality_comparable_v::iterator>); + static_assert(entt::is_equality_comparable_v); + + static_assert(!entt::is_equality_comparable_v); + static_assert(!entt::is_equality_comparable_v); + static_assert(!entt::is_equality_comparable_v>); + static_assert(!entt::is_equality_comparable_v>>); + static_assert(!entt::is_equality_comparable_v>); + static_assert(!entt::is_equality_comparable_v>>); + static_assert(!entt::is_equality_comparable_v>); + static_assert(!entt::is_equality_comparable_v>>); + static_assert(!entt::is_equality_comparable_v); +} + +TEST(ConstnessAs, Functionalities) { + static_assert(std::is_same_v, int>); + static_assert(std::is_same_v, int>); + static_assert(std::is_same_v, const int>); + static_assert(std::is_same_v, const int>); +} + +TEST(MemberClass, Functionalities) { + struct clazz { + char foo(int) { + return {}; + } + + int bar(double, float) const { + return {}; + } + + bool quux; + }; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); +} + +TEST(Tag, Functionalities) { + using namespace entt::literals; + static_assert(entt::tag<"foobar"_hs>::value == entt::hashed_string::value("foobar")); + static_assert(std::is_same_v::value_type, entt::id_type>); } diff --git a/modules/entt/test/entt/core/utility.cpp b/modules/entt/test/entt/core/utility.cpp index 45987a3..4acab12 100644 --- a/modules/entt/test/entt/core/utility.cpp +++ b/modules/entt/test/entt/core/utility.cpp @@ -1,7 +1,9 @@ +#include #include +#include #include -struct Functions { +struct functions { static void foo(int) {} static void foo() {} @@ -9,11 +11,51 @@ struct Functions { void bar() {} }; +TEST(Identity, Functionalities) { + entt::identity identity; + int value = 42; -TEST(Utility, Overload) { - ASSERT_EQ(entt::overload(&Functions::foo), static_cast(&Functions::foo)); - ASSERT_EQ(entt::overload(&Functions::foo), static_cast(&Functions::foo)); + ASSERT_TRUE(entt::is_transparent_v); + ASSERT_EQ(identity(value), value); + ASSERT_EQ(&identity(value), &value); +} + +TEST(Overload, Functionalities) { + ASSERT_EQ(entt::overload(&functions::foo), static_cast(&functions::foo)); + ASSERT_EQ(entt::overload(&functions::foo), static_cast(&functions::foo)); + + ASSERT_EQ(entt::overload(&functions::bar), static_cast(&functions::bar)); + ASSERT_EQ(entt::overload(&functions::bar), static_cast(&functions::bar)); + + functions instance; + + ASSERT_NO_FATAL_FAILURE(entt::overload(&functions::foo)(0)); + ASSERT_NO_FATAL_FAILURE(entt::overload(&functions::foo)()); + + ASSERT_NO_FATAL_FAILURE((instance.*entt::overload(&functions::bar))(0)); + ASSERT_NO_FATAL_FAILURE((instance.*entt::overload(&functions::bar))()); +} + +TEST(Overloaded, Functionalities) { + int iv = 0; + char cv = '\0'; + + entt::overloaded func{ + [&iv](int value) { iv = value; }, + [&cv](char value) { cv = value; }}; + + func(42); + func('c'); + + ASSERT_EQ(iv, 42); + ASSERT_EQ(cv, 'c'); +} + +TEST(YCombinator, Functionalities) { + entt::y_combinator gauss([](const auto &self, auto value) -> unsigned int { + return value ? (value + self(value - 1u)) : 0; + }); - ASSERT_EQ(entt::overload(&Functions::bar), static_cast(&Functions::bar)); - ASSERT_EQ(entt::overload(&Functions::bar), static_cast(&Functions::bar)); + ASSERT_EQ(gauss(3u), 3u * 4u / 2u); + ASSERT_EQ(std::as_const(gauss)(7u), 7u * 8u / 2u); } diff --git a/modules/entt/test/entt/entity/actor.cpp b/modules/entt/test/entt/entity/actor.cpp deleted file mode 100644 index ca9d924..0000000 --- a/modules/entt/test/entt/entity/actor.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include -#include -#include -#include - -TEST(Actor, Component) { - entt::registry registry; - entt::actor actor{registry}; - - ASSERT_EQ(®istry, &actor.backend()); - ASSERT_EQ(®istry, &std::as_const(actor).backend()); - ASSERT_TRUE(registry.empty()); - ASSERT_FALSE(registry.empty()); - ASSERT_FALSE(actor.has()); - - const auto &cint = actor.assign(); - const auto &cchar = actor.assign(); - - ASSERT_EQ(&cint, &actor.get()); - ASSERT_EQ(&cchar, &std::as_const(actor).get()); - ASSERT_EQ(&cint, &std::get<0>(actor.get())); - ASSERT_EQ(&cchar, &std::get<1>(actor.get())); - ASSERT_EQ(&cint, std::get<0>(actor.try_get())); - ASSERT_EQ(&cchar, std::get<1>(actor.try_get())); - ASSERT_EQ(nullptr, std::get<2>(actor.try_get())); - ASSERT_EQ(nullptr, actor.try_get()); - ASSERT_EQ(&cchar, actor.try_get()); - ASSERT_EQ(&cint, actor.try_get()); - - ASSERT_FALSE(registry.empty()); - ASSERT_FALSE(registry.empty()); - ASSERT_TRUE(actor.has()); - ASSERT_TRUE(actor.has()); - ASSERT_FALSE(actor.has()); - - actor.remove(); - - ASSERT_TRUE(registry.empty()); - ASSERT_FALSE(registry.empty()); - ASSERT_FALSE(actor.has()); -} - -TEST(Actor, EntityLifetime) { - entt::registry registry; - auto *actor = new entt::actor{registry}; - actor->assign(); - - ASSERT_FALSE(registry.empty()); - ASSERT_FALSE(registry.empty()); - - registry.each([actor](const auto entity) { - ASSERT_EQ(actor->entity(), entity); - }); - - delete actor; - - ASSERT_TRUE(registry.empty()); - ASSERT_TRUE(registry.empty()); -} diff --git a/modules/entt/test/entt/entity/component.cpp b/modules/entt/test/entt/entity/component.cpp new file mode 100644 index 0000000..06c27d2 --- /dev/null +++ b/modules/entt/test/entt/entity/component.cpp @@ -0,0 +1,52 @@ +#include +#include + +struct self_contained { + static constexpr auto in_place_delete = true; + static constexpr auto page_size = 4u; +}; + +struct traits_based {}; + +template<> +struct entt::component_traits { + static constexpr auto in_place_delete = false; + static constexpr auto page_size = 8u; +}; + +struct default_params_empty {}; +struct default_params_non_empty { + int value; +}; + +TEST(Component, DefaultParamsEmpty) { + using traits = entt::component_traits; + + static_assert(!traits::in_place_delete); + static_assert(traits::page_size == 0u); + static_assert(entt::ignore_as_empty_v); +} + +TEST(Component, DefaultParamsNonEmpty) { + using traits = entt::component_traits; + + static_assert(!traits::in_place_delete); + static_assert(traits::page_size == ENTT_PACKED_PAGE); + static_assert(!entt::ignore_as_empty_v); +} + +TEST(Component, SelfContained) { + using traits = entt::component_traits; + + static_assert(traits::in_place_delete); + static_assert(traits::page_size == 4u); + static_assert(!entt::ignore_as_empty_v); +} + +TEST(Component, TraitsBased) { + using traits = entt::component_traits; + + static_assert(!traits::in_place_delete); + static_assert(traits::page_size == 8u); + static_assert(!entt::ignore_as_empty_v); +} diff --git a/modules/entt/test/entt/entity/entity.cpp b/modules/entt/test/entt/entity/entity.cpp index 42dc3b0..b8212bc 100644 --- a/modules/entt/test/entt/entity/entity.cpp +++ b/modules/entt/test/entt/entity/entity.cpp @@ -1,24 +1,92 @@ -#include #include #include #include -TEST(Traits, Null) { +TEST(Entity, Traits) { + using traits_type = entt::entt_traits; + constexpr entt::entity tombstone = entt::tombstone; + constexpr entt::entity null = entt::null; entt::registry registry{}; + registry.destroy(registry.create()); const auto entity = registry.create(); - registry.assign(entity, 42); + const auto other = registry.create(); - ASSERT_TRUE(~entt::entity{} == entt::null); + ASSERT_EQ(entt::to_integral(entity), entt::to_integral(entity)); + ASSERT_NE(entt::to_integral(entity), entt::to_integral(entt::null)); + ASSERT_NE(entt::to_integral(entity), entt::to_integral(entt::entity{})); + ASSERT_EQ(entt::to_entity(entity), 0u); + ASSERT_EQ(entt::to_version(entity), 1u); + ASSERT_EQ(entt::to_entity(other), 1u); + ASSERT_EQ(entt::to_version(other), 0u); + + ASSERT_EQ(traits_type::construct(entt::to_entity(entity), entt::to_version(entity)), entity); + ASSERT_EQ(traits_type::construct(entt::to_entity(other), entt::to_version(other)), other); + ASSERT_NE(traits_type::construct(entt::to_entity(entity), {}), entity); + + ASSERT_EQ(traits_type::construct(entt::to_entity(other), entt::to_version(entity)), traits_type::combine(entt::to_integral(other), entt::to_integral(entity))); + + ASSERT_EQ(traits_type::combine(entt::tombstone, entt::null), tombstone); + ASSERT_EQ(traits_type::combine(entt::null, entt::tombstone), null); +} + +TEST(Entity, Null) { + using traits_type = entt::entt_traits; + constexpr entt::entity null = entt::null; + + ASSERT_FALSE(entt::entity{} == entt::null); ASSERT_TRUE(entt::null == entt::null); ASSERT_FALSE(entt::null != entt::null); + entt::registry registry{}; + const auto entity = registry.create(); + + ASSERT_EQ(traits_type::combine(entt::null, entt::to_integral(entity)), (traits_type::construct(entt::to_entity(null), entt::to_version(entity)))); + ASSERT_EQ(traits_type::combine(entt::null, entt::to_integral(null)), null); + ASSERT_EQ(traits_type::combine(entt::null, entt::tombstone), null); + + registry.emplace(entity, 42); + ASSERT_FALSE(entity == entt::null); ASSERT_FALSE(entt::null == entity); ASSERT_TRUE(entity != entt::null); ASSERT_TRUE(entt::null != entity); - ASSERT_FALSE(registry.valid(entt::null)); + const entt::entity other = entt::null; + + ASSERT_FALSE(registry.valid(other)); + ASSERT_NE(registry.create(other), other); +} + +TEST(Entity, Tombstone) { + using traits_type = entt::entt_traits; + constexpr entt::entity tombstone = entt::tombstone; + + ASSERT_FALSE(entt::entity{} == entt::tombstone); + ASSERT_TRUE(entt::tombstone == entt::tombstone); + ASSERT_FALSE(entt::tombstone != entt::tombstone); + + entt::registry registry{}; + const auto entity = registry.create(); + + ASSERT_EQ(traits_type::combine(entt::to_integral(entity), entt::tombstone), (traits_type::construct(entt::to_entity(entity), entt::to_version(tombstone)))); + ASSERT_EQ(traits_type::combine(entt::tombstone, entt::to_integral(tombstone)), tombstone); + ASSERT_EQ(traits_type::combine(entt::tombstone, entt::null), tombstone); + + registry.emplace(entity, 42); + + ASSERT_FALSE(entity == entt::tombstone); + ASSERT_FALSE(entt::tombstone == entity); + + ASSERT_TRUE(entity != entt::tombstone); + ASSERT_TRUE(entt::tombstone != entity); + + constexpr auto vers = entt::to_version(tombstone); + const auto other = traits_type::construct(entt::to_entity(entity), vers); + + ASSERT_FALSE(registry.valid(entt::tombstone)); + ASSERT_NE(registry.destroy(entity, vers), vers); + ASSERT_NE(registry.create(other), other); } diff --git a/modules/entt/test/entt/entity/group.cpp b/modules/entt/test/entt/entity/group.cpp index 9b88a45..08494e8 100644 --- a/modules/entt/test/entt/entity/group.cpp +++ b/modules/entt/test/entt/entity/group.cpp @@ -1,57 +1,57 @@ -#include -#include #include +#include +#include +#include +#include +#include +#include #include -#include -#include #include +#include struct empty_type {}; -struct boxed_int { int value; }; + +struct boxed_int { + int value; +}; + +bool operator==(const boxed_int &lhs, const boxed_int &rhs) { + return lhs.value == rhs.value; +} TEST(NonOwningGroup, Functionalities) { entt::registry registry; auto group = registry.group(entt::get); - auto cgroup = std::as_const(registry).group(entt::get); + auto cgroup = std::as_const(registry).group_if_exists(entt::get); ASSERT_TRUE(group.empty()); - ASSERT_TRUE(group.empty()); - ASSERT_TRUE(cgroup.empty()); const auto e0 = registry.create(); - registry.assign(e0); + registry.emplace(e0, '1'); const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(e1, 42); + registry.emplace(e1, '2'); ASSERT_FALSE(group.empty()); - ASSERT_FALSE(group.empty()); - ASSERT_FALSE(cgroup.empty()); - ASSERT_NO_THROW((group.begin()++)); - ASSERT_NO_THROW((++cgroup.begin())); + ASSERT_NO_FATAL_FAILURE(group.begin()++); + ASSERT_NO_FATAL_FAILURE(++cgroup.begin()); + ASSERT_NO_FATAL_FAILURE([](auto it) { return it++; }(group.rbegin())); + ASSERT_NO_FATAL_FAILURE([](auto it) { return ++it; }(cgroup.rbegin())); ASSERT_NE(group.begin(), group.end()); ASSERT_NE(cgroup.begin(), cgroup.end()); - ASSERT_EQ(group.size(), typename decltype(group)::size_type{1}); - ASSERT_EQ(group.size(), typename decltype(group)::size_type{1}); - ASSERT_EQ(cgroup.size(), typename decltype(group)::size_type{2}); + ASSERT_NE(group.rbegin(), group.rend()); + ASSERT_NE(cgroup.rbegin(), cgroup.rend()); + ASSERT_EQ(group.size(), 1u); - registry.assign(e0); + registry.emplace(e0); - ASSERT_EQ(group.size(), typename decltype(group)::size_type{2}); - ASSERT_EQ(group.size(), typename decltype(group)::size_type{2}); - ASSERT_EQ(cgroup.size(), typename decltype(group)::size_type{2}); + ASSERT_EQ(group.size(), 2u); - registry.remove(e0); + registry.erase(e0); - ASSERT_EQ(group.size(), typename decltype(group)::size_type{1}); - ASSERT_EQ(group.size(), typename decltype(group)::size_type{1}); - ASSERT_EQ(cgroup.size(), typename decltype(group)::size_type{2}); - - registry.get(e0) = '1'; - registry.get(e1) = '2'; - registry.get(e1) = 42; + ASSERT_EQ(group.size(), 1u); for(auto entity: group) { ASSERT_EQ(std::get<0>(cgroup.get(entity)), 42); @@ -59,44 +59,87 @@ TEST(NonOwningGroup, Functionalities) { ASSERT_EQ(cgroup.get(entity), '2'); } - ASSERT_EQ(*(group.data() + 0), e1); - - ASSERT_EQ(*(group.data() + 0), e1); - ASSERT_EQ(*(group.data() + 0), e0); - ASSERT_EQ(*(cgroup.data() + 1), e1); + ASSERT_EQ(group.handle().data()[0u], e1); - ASSERT_EQ(*(group.raw() + 0), 42); - ASSERT_EQ(*(group.raw() + 0), '1'); - ASSERT_EQ(*(cgroup.raw() + 1), '2'); - - registry.remove(e0); - registry.remove(e1); + registry.erase(e0); + registry.erase(e1); ASSERT_EQ(group.begin(), group.end()); ASSERT_EQ(cgroup.begin(), cgroup.end()); + ASSERT_EQ(group.rbegin(), group.rend()); + ASSERT_EQ(cgroup.rbegin(), cgroup.rend()); ASSERT_TRUE(group.empty()); - ASSERT_TRUE(group.capacity()); + ASSERT_NE(group.capacity(), 0u); group.shrink_to_fit(); - ASSERT_FALSE(group.capacity()); + ASSERT_EQ(group.capacity(), 0u); + + decltype(group) invalid{}; + + ASSERT_TRUE(group); + ASSERT_TRUE(cgroup); + ASSERT_FALSE(invalid); +} + +TEST(NonOwningGroup, Handle) { + entt::registry registry; + const auto entity = registry.create(); + + auto group = registry.group(entt::get); + auto &&handle = group.handle(); + + ASSERT_TRUE(handle.empty()); + ASSERT_FALSE(handle.contains(entity)); + ASSERT_EQ(&handle, &group.handle()); + + registry.emplace(entity); + registry.emplace(entity); + + ASSERT_FALSE(handle.empty()); + ASSERT_TRUE(handle.contains(entity)); + ASSERT_EQ(&handle, &group.handle()); +} + +TEST(NonOwningGroup, Invalid) { + entt::registry registry{}; + auto group = std::as_const(registry).group_if_exists(entt::get); + + const auto entity = registry.create(); + registry.emplace(entity); + registry.emplace(entity); + + ASSERT_FALSE(group); + + ASSERT_TRUE(group.empty()); + ASSERT_EQ(group.size(), 0u); + ASSERT_EQ(group.capacity(), 0u); + ASSERT_NO_FATAL_FAILURE(group.shrink_to_fit()); + + ASSERT_EQ(group.begin(), group.end()); + ASSERT_EQ(group.rbegin(), group.rend()); + + ASSERT_FALSE(group.contains(entity)); + ASSERT_EQ(group.find(entity), group.end()); + ASSERT_EQ(group.front(), entt::entity{entt::null}); + ASSERT_EQ(group.back(), entt::entity{entt::null}); } TEST(NonOwningGroup, ElementAccess) { entt::registry registry; auto group = registry.group(entt::get); - auto cgroup = std::as_const(registry).group(entt::get); + auto cgroup = std::as_const(registry).group_if_exists(entt::get); const auto e0 = registry.create(); - registry.assign(e0); - registry.assign(e0); + registry.emplace(e0); + registry.emplace(e0); const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(e1); + registry.emplace(e1); - for(typename decltype(group)::size_type i{}; i < group.size(); ++i) { + for(auto i = 0u; i < group.size(); ++i) { ASSERT_EQ(group[i], i ? e0 : e1); ASSERT_EQ(cgroup[i], i ? e0 : e1); } @@ -107,12 +150,12 @@ TEST(NonOwningGroup, Contains) { auto group = registry.group(entt::get); const auto e0 = registry.create(); - registry.assign(e0); - registry.assign(e0); + registry.emplace(e0); + registry.emplace(e0); const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(e1); + registry.emplace(e1); registry.destroy(e0); @@ -124,49 +167,66 @@ TEST(NonOwningGroup, Empty) { entt::registry registry; const auto e0 = registry.create(); - registry.assign(e0); - registry.assign(e0); - registry.assign(e0); + registry.emplace(e0); + registry.emplace(e0); + registry.emplace(e0); const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(e1); + registry.emplace(e1); - for(auto entity: registry.group(entt::get)) { - (void)entity; - FAIL(); - } - - for(auto entity: registry.group(entt::get)) { - (void)entity; - FAIL(); - } + ASSERT_TRUE(registry.group(entt::get).empty()); + ASSERT_TRUE(registry.group(entt::get).empty()); } TEST(NonOwningGroup, Each) { entt::registry registry; + entt::entity entity[2]{registry.create(), registry.create()}; + auto group = registry.group(entt::get); + auto cgroup = std::as_const(registry).group_if_exists(entt::get); - const auto e0 = registry.create(); - registry.assign(e0); - registry.assign(e0); + registry.emplace(entity[0u], 0); + registry.emplace(entity[0u], 0); - const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(entity[1u], 1); + registry.emplace(entity[1u], 1); - auto cgroup = std::as_const(registry).group(entt::get); - std::size_t cnt = 0; + auto iterable = group.each(); + auto citerable = cgroup.each(); - group.each([&cnt](auto, int &, char &) { ++cnt; }); - group.each([&cnt](int &, char &) { ++cnt; }); + ASSERT_NE(citerable.begin(), citerable.end()); + ASSERT_NO_THROW(iterable.begin()->operator=(*iterable.begin())); + ASSERT_EQ(decltype(iterable.end()){}, iterable.end()); - ASSERT_EQ(cnt, std::size_t{4}); + auto it = iterable.begin(); - cgroup.each([&cnt](auto, const int &, const char &) { --cnt; }); - cgroup.each([&cnt](const int &, const char &) { --cnt; }); + ASSERT_EQ((it++, ++it), iterable.end()); + + group.each([expected = 1u](auto entt, int &ivalue, char &cvalue) mutable { + ASSERT_EQ(entt::to_integral(entt), expected); + ASSERT_EQ(ivalue, expected); + ASSERT_EQ(cvalue, expected); + --expected; + }); + + cgroup.each([expected = 1u](const int &ivalue, const char &cvalue) mutable { + ASSERT_EQ(ivalue, expected); + ASSERT_EQ(cvalue, expected); + --expected; + }); - ASSERT_EQ(cnt, std::size_t{0}); + ASSERT_EQ(std::get<0>(*iterable.begin()), entity[1u]); + ASSERT_EQ(std::get<0>(*++citerable.begin()), entity[0u]); + + static_assert(std::is_same_v(*iterable.begin())), int &>); + static_assert(std::is_same_v(*citerable.begin())), const char &>); + + // do not use iterable, make sure an iterable group works when created from a temporary + for(auto [entt, ivalue, cvalue]: registry.group(entt::get).each()) { + ASSERT_EQ(entt::to_integral(entt), ivalue); + ASSERT_EQ(entt::to_integral(entt), cvalue); + } } TEST(NonOwningGroup, Sort) { @@ -176,17 +236,86 @@ TEST(NonOwningGroup, Sort) { const auto e0 = registry.create(); const auto e1 = registry.create(); const auto e2 = registry.create(); + const auto e3 = registry.create(); + + registry.emplace(e0, 0u); + registry.emplace(e1, 1u); + registry.emplace(e2, 2u); + registry.emplace(e3, 3u); + + registry.emplace(e0, 0); + registry.emplace(e1, 1); + registry.emplace(e2, 2); + + ASSERT_EQ(group.handle().data()[0u], e0); + ASSERT_EQ(group.handle().data()[1u], e1); + ASSERT_EQ(group.handle().data()[2u], e2); + + group.sort([](const entt::entity lhs, const entt::entity rhs) { + return entt::to_integral(lhs) < entt::to_integral(rhs); + }); + + ASSERT_EQ(group.handle().data()[0u], e2); + ASSERT_EQ(group.handle().data()[1u], e1); + ASSERT_EQ(group.handle().data()[2u], e0); + + ASSERT_EQ((group.get(e0)), (std::make_tuple(0, 0u))); + ASSERT_EQ((group.get(e1)), (std::make_tuple(1, 1u))); + ASSERT_EQ((group.get(e2)), (std::make_tuple(2, 2u))); + + ASSERT_FALSE(group.contains(e3)); + + group.sort([](const int lhs, const int rhs) { + return lhs > rhs; + }); + + ASSERT_EQ(group.handle().data()[0u], e0); + ASSERT_EQ(group.handle().data()[1u], e1); + ASSERT_EQ(group.handle().data()[2u], e2); + + ASSERT_EQ((group.get(e0)), (std::make_tuple(0, 0u))); + ASSERT_EQ((group.get(e1)), (std::make_tuple(1, 1u))); + ASSERT_EQ((group.get(e2)), (std::make_tuple(2, 2u))); + + ASSERT_FALSE(group.contains(e3)); + + group.sort([](const auto lhs, const auto rhs) { + static_assert(std::is_same_v(lhs)), const int &>); + static_assert(std::is_same_v(rhs)), unsigned int &>); + return std::get<0>(lhs) < std::get<0>(rhs); + }); + + ASSERT_EQ(group.handle().data()[0u], e2); + ASSERT_EQ(group.handle().data()[1u], e1); + ASSERT_EQ(group.handle().data()[2u], e0); + + ASSERT_EQ((group.get(e0)), (std::make_tuple(0, 0u))); + ASSERT_EQ((group.get(e1)), (std::make_tuple(1, 1u))); + ASSERT_EQ((group.get(e2)), (std::make_tuple(2, 2u))); + + ASSERT_FALSE(group.contains(e3)); +} + +TEST(NonOwningGroup, SortAsAPool) { + entt::registry registry; + auto group = registry.group(entt::get); + + const auto e0 = registry.create(); + const auto e1 = registry.create(); + const auto e2 = registry.create(); + const auto e3 = registry.create(); auto uval = 0u; auto ival = 0; - registry.assign(e0, uval++); - registry.assign(e1, uval++); - registry.assign(e2, uval++); + registry.emplace(e0, uval++); + registry.emplace(e1, uval++); + registry.emplace(e2, uval++); + registry.emplace(e3, uval + 1); - registry.assign(e0, ival++); - registry.assign(e1, ival++); - registry.assign(e2, ival++); + registry.emplace(e0, ival++); + registry.emplace(e1, ival++); + registry.emplace(e2, ival++); for(auto entity: group) { ASSERT_EQ(group.get(entity), --uval); @@ -196,6 +325,12 @@ TEST(NonOwningGroup, Sort) { registry.sort(std::less{}); group.sort(); + ASSERT_EQ((group.get(e0)), (std::make_tuple(0, 0u))); + ASSERT_EQ((group.get(e1)), (std::make_tuple(1, 1u))); + ASSERT_EQ((group.get(e2)), (std::make_tuple(2, 2u))); + + ASSERT_FALSE(group.contains(e3)); + for(auto entity: group) { ASSERT_EQ(group.get(entity), uval++); ASSERT_EQ(group.get(entity), ival++); @@ -209,16 +344,16 @@ TEST(NonOwningGroup, IndexRebuiltOnDestroy) { const auto e0 = registry.create(); const auto e1 = registry.create(); - registry.assign(e0, 0u); - registry.assign(e1, 1u); + registry.emplace(e0, 0u); + registry.emplace(e1, 1u); - registry.assign(e0, 0); - registry.assign(e1, 1); + registry.emplace(e0, 0); + registry.emplace(e1, 1); registry.destroy(e0); - registry.assign(registry.create(), 42); + registry.emplace(registry.create(), 42); - ASSERT_EQ(group.size(), typename decltype(group)::size_type{1}); + ASSERT_EQ(group.size(), 1u); ASSERT_EQ(group[{}], e1); ASSERT_EQ(group.get(e1), 1); ASSERT_EQ(group.get(e1), 1u); @@ -228,30 +363,46 @@ TEST(NonOwningGroup, IndexRebuiltOnDestroy) { ASSERT_EQ(ivalue, 1); ASSERT_EQ(uivalue, 1u); }); + + for(auto &&curr: group.each()) { + ASSERT_EQ(std::get<0>(curr), e1); + ASSERT_EQ(std::get<1>(curr), 1); + ASSERT_EQ(std::get<2>(curr), 1u); + } } TEST(NonOwningGroup, ConstNonConstAndAllInBetween) { entt::registry registry; - auto group = registry.group(entt::get); + auto group = registry.group(entt::get); - ASSERT_EQ(group.size(), decltype(group.size()){0}); + ASSERT_EQ(group.size(), 0u); const auto entity = registry.create(); - registry.assign(entity, 0); - registry.assign(entity, 'c'); + registry.emplace(entity, 0); + registry.emplace(entity); + registry.emplace(entity, 'c'); + + ASSERT_EQ(group.size(), 1u); - ASSERT_EQ(group.size(), decltype(group.size()){1}); + static_assert(std::is_same_v({})), int &>); + static_assert(std::is_same_v({})), const char &>); + static_assert(std::is_same_v({})), std::tuple>); + static_assert(std::is_same_v>); - ASSERT_TRUE((std::is_same_v(0)), int &>)); - ASSERT_TRUE((std::is_same_v(0)), const char &>)); - ASSERT_TRUE((std::is_same_v(0)), std::tuple>)); - ASSERT_TRUE((std::is_same_v()), const char *>)); - ASSERT_TRUE((std::is_same_v()), int *>)); + static_assert(std::is_same_v)), decltype(std::as_const(registry).group_if_exists(entt::get))>); + static_assert(std::is_same_v)), decltype(std::as_const(registry).group_if_exists(entt::get))>); + static_assert(std::is_same_v)), decltype(std::as_const(registry).group_if_exists(entt::get))>); - group.each([](auto, auto &&i, auto &&c) { - ASSERT_TRUE((std::is_same_v)); - ASSERT_TRUE((std::is_same_v)); + group.each([](auto &&i, auto &&c) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); }); + + for(auto [entt, iv, cv]: group.each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } } TEST(NonOwningGroup, Find) { @@ -259,22 +410,22 @@ TEST(NonOwningGroup, Find) { auto group = registry.group(entt::get); const auto e0 = registry.create(); - registry.assign(e0); - registry.assign(e0); + registry.emplace(e0); + registry.emplace(e0); const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(e1); + registry.emplace(e1); const auto e2 = registry.create(); - registry.assign(e2); - registry.assign(e2); + registry.emplace(e2); + registry.emplace(e2); const auto e3 = registry.create(); - registry.assign(e3); - registry.assign(e3); + registry.emplace(e3); + registry.emplace(e3); - registry.remove(e1); + registry.erase(e1); ASSERT_NE(group.find(e0), group.end()); ASSERT_EQ(group.find(e1), group.end()); @@ -292,8 +443,8 @@ TEST(NonOwningGroup, Find) { const auto e4 = registry.create(); registry.destroy(e4); const auto e5 = registry.create(); - registry.assign(e5); - registry.assign(e5); + registry.emplace(e5); + registry.emplace(e5); ASSERT_NE(group.find(e5), group.end()); ASSERT_EQ(group.find(e4), group.end()); @@ -303,46 +454,46 @@ TEST(NonOwningGroup, ExcludedComponents) { entt::registry registry; const auto e0 = registry.create(); - registry.assign(e0, 0); + registry.emplace(e0, 0); const auto e1 = registry.create(); - registry.assign(e1, 1); - registry.assign(e1); + registry.emplace(e1, 1); + registry.emplace(e1); const auto group = registry.group(entt::get, entt::exclude); const auto e2 = registry.create(); - registry.assign(e2, 2); + registry.emplace(e2, 2); const auto e3 = registry.create(); - registry.assign(e3, 3); - registry.assign(e3); + registry.emplace(e3, 3); + registry.emplace(e3); for(const auto entity: group) { + ASSERT_TRUE(entity == e0 || entity == e2); + if(entity == e0) { ASSERT_EQ(group.get(e0), 0); } else if(entity == e2) { ASSERT_EQ(group.get(e2), 2); - } else { - FAIL(); } } - registry.assign(e0); - registry.assign(e2); + registry.emplace(e0); + registry.emplace(e2); ASSERT_TRUE(group.empty()); - registry.remove(e1); - registry.remove(e3); + registry.erase(e1); + registry.erase(e3); for(const auto entity: group) { + ASSERT_TRUE(entity == e1 || entity == e3); + if(entity == e1) { ASSERT_EQ(group.get(e1), 1); } else if(entity == e3) { ASSERT_EQ(group.get(e3), 3); - } else { - FAIL(); } } } @@ -352,113 +503,226 @@ TEST(NonOwningGroup, EmptyAndNonEmptyTypes) { const auto group = registry.group(entt::get); const auto e0 = registry.create(); - registry.assign(e0); - registry.assign(e0); + registry.emplace(e0); + registry.emplace(e0); const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(e1); + registry.emplace(e1); - registry.assign(registry.create()); + registry.emplace(registry.create()); for(const auto entity: group) { ASSERT_TRUE(entity == e0 || entity == e1); } - group.each([e0, e1](const auto entity, const int &, empty_type) { + group.each([e0, e1](const auto entity, const int &) { ASSERT_TRUE(entity == e0 || entity == e1); }); - ASSERT_EQ(group.size(), typename decltype(group)::size_type{2}); + for(auto [entt, iv]: group.each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_TRUE(entt == e0 || entt == e1); + } + + ASSERT_EQ(group.size(), 2u); } TEST(NonOwningGroup, TrackEntitiesOnComponentDestruction) { entt::registry registry; const auto group = registry.group(entt::get, entt::exclude); - const auto cgroup = std::as_const(registry).group(entt::get, entt::exclude); + const auto cgroup = std::as_const(registry).group_if_exists(entt::get, entt::exclude); const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); + registry.emplace(entity); + registry.emplace(entity); ASSERT_TRUE(group.empty()); ASSERT_TRUE(cgroup.empty()); - registry.remove(entity); + registry.erase(entity); ASSERT_FALSE(group.empty()); ASSERT_FALSE(cgroup.empty()); } -TEST(NonOwningGroup, Less) { +TEST(NonOwningGroup, EmptyTypes) { entt::registry registry; - const auto entity = std::get<0>(registry.create>()); - registry.create(); + const auto entity = registry.create(); - registry.group(entt::get>).less([entity](const auto entt, int, char) { + registry.emplace(entity); + registry.emplace(entity); + registry.emplace(entity); + + registry.group(entt::get).each([entity](const auto entt, int, char) { ASSERT_EQ(entity, entt); }); - registry.group(entt::get, char>).less([check = true](int, char) mutable { + for(auto [entt, iv, cv]: registry.group(entt::get).each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_EQ(entity, entt); + } + + registry.group(entt::get).each([check = true](int, char) mutable { ASSERT_TRUE(check); check = false; }); - registry.group(entt::get, int, char>).less([entity](const auto entt, int, char) { + for(auto [entt, iv, cv]: registry.group(entt::get).each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); ASSERT_EQ(entity, entt); - }); + } - registry.group(entt::get).less([entity](const auto entt, int, char, double) { + registry.group(entt::get).each([entity](const auto entt, int, char) { ASSERT_EQ(entity, entt); }); + + for(auto [entt, iv, cv]: registry.group(entt::get).each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_EQ(entity, entt); + } + + auto iterable = registry.group(entt::get).each(); + + ASSERT_EQ(iterable.begin(), iterable.end()); +} + +TEST(NonOwningGroup, FrontBack) { + entt::registry registry; + auto group = registry.group<>(entt::get); + + ASSERT_EQ(group.front(), static_cast(entt::null)); + ASSERT_EQ(group.back(), static_cast(entt::null)); + + const auto e0 = registry.create(); + registry.emplace(e0); + registry.emplace(e0); + + const auto e1 = registry.create(); + registry.emplace(e1); + registry.emplace(e1); + + const auto entity = registry.create(); + registry.emplace(entity); + + ASSERT_EQ(group.front(), e1); + ASSERT_EQ(group.back(), e0); +} + +TEST(NonOwningGroup, SignalRace) { + entt::registry registry; + registry.on_construct().connect<&entt::registry::emplace_or_replace>(); + const auto group = registry.group(entt::get); + + auto entity = registry.create(); + registry.emplace(entity); + + ASSERT_EQ(group.size(), 1u); +} + +TEST(NonOwningGroup, ExtendedGet) { + using type = decltype(std::declval().group(entt::get).get({})); + + static_assert(std::tuple_size_v == 2u); + static_assert(std::is_same_v, int &>); + static_assert(std::is_same_v, char &>); + + entt::registry registry; + const auto entity = registry.create(); + + registry.emplace(entity, 42); + registry.emplace(entity, 'c'); + + const auto tup = registry.group(entt::get).get(entity); + + ASSERT_EQ(std::get<0>(tup), 42); + ASSERT_EQ(std::get<1>(tup), 'c'); +} + +TEST(NonOwningGroup, IterableGroupAlgorithmCompatibility) { + entt::registry registry; + const auto entity = registry.create(); + + registry.emplace(entity); + registry.emplace(entity); + + const auto group = registry.group(entt::get); + const auto iterable = group.each(); + const auto it = std::find_if(iterable.begin(), iterable.end(), [entity](auto args) { return std::get<0>(args) == entity; }); + + ASSERT_EQ(std::get<0>(*it), entity); +} + +TEST(NonOwningGroup, Storage) { + entt::registry registry; + const auto entity = registry.create(); + const auto group = registry.group(entt::get); + + static_assert(std::is_same_v()), typename entt::storage_traits::storage_type &>); + static_assert(std::is_same_v()), typename entt::storage_traits::storage_type &>); + static_assert(std::is_same_v()), const typename entt::storage_traits::storage_type &>); + static_assert(std::is_same_v()), const typename entt::storage_traits::storage_type &>); + + ASSERT_EQ(group.size(), 0u); + + group.storage().emplace(entity); + registry.emplace(entity); + + ASSERT_EQ(group.size(), 1u); + ASSERT_TRUE(group.storage().contains(entity)); + ASSERT_TRUE(group.storage().contains(entity)); + ASSERT_TRUE((registry.all_of(entity))); + + group.storage<0u>().erase(entity); + + ASSERT_EQ(group.size(), 0u); + ASSERT_TRUE(group.storage<1u>().contains(entity)); + ASSERT_FALSE((registry.all_of(entity))); } TEST(OwningGroup, Functionalities) { entt::registry registry; auto group = registry.group(entt::get); - auto cgroup = std::as_const(registry).group(entt::get); + auto cgroup = std::as_const(registry).group_if_exists(entt::get); ASSERT_TRUE(group.empty()); - ASSERT_TRUE(group.empty()); - ASSERT_TRUE(cgroup.empty()); const auto e0 = registry.create(); - registry.assign(e0); + registry.emplace(e0, '1'); const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(e1, 42); + registry.emplace(e1, '2'); ASSERT_FALSE(group.empty()); - ASSERT_FALSE(group.empty()); - ASSERT_FALSE(cgroup.empty()); - ASSERT_NO_THROW((group.begin()++)); - ASSERT_NO_THROW((++cgroup.begin())); + ASSERT_NO_FATAL_FAILURE(group.begin()++); + ASSERT_NO_FATAL_FAILURE(++cgroup.begin()); + ASSERT_NO_FATAL_FAILURE([](auto it) { return it++; }(group.rbegin())); + ASSERT_NO_FATAL_FAILURE([](auto it) { return ++it; }(cgroup.rbegin())); ASSERT_NE(group.begin(), group.end()); ASSERT_NE(cgroup.begin(), cgroup.end()); - ASSERT_EQ(group.size(), typename decltype(group)::size_type{1}); - ASSERT_EQ(group.size(), typename decltype(group)::size_type{1}); - ASSERT_EQ(cgroup.size(), typename decltype(group)::size_type{2}); - - registry.assign(e0); + ASSERT_NE(group.rbegin(), group.rend()); + ASSERT_NE(cgroup.rbegin(), cgroup.rend()); + ASSERT_EQ(group.size(), 1u); - ASSERT_EQ(group.size(), typename decltype(group)::size_type{2}); - ASSERT_EQ(group.size(), typename decltype(group)::size_type{2}); - ASSERT_EQ(cgroup.size(), typename decltype(group)::size_type{2}); + registry.emplace(e0); - registry.remove(e0); + ASSERT_EQ(group.size(), 2u); - ASSERT_EQ(group.size(), typename decltype(group)::size_type{1}); - ASSERT_EQ(group.size(), typename decltype(group)::size_type{1}); - ASSERT_EQ(cgroup.size(), typename decltype(group)::size_type{2}); + registry.erase(e0); - registry.get(e0) = '1'; - registry.get(e1) = '2'; - registry.get(e1) = 42; + ASSERT_EQ(group.size(), 1u); - ASSERT_EQ(*(cgroup.raw() + 0), 42); - ASSERT_EQ(*(group.raw() + 0), 42); + ASSERT_EQ(cgroup.storage().raw()[0u][0u], 42); + ASSERT_EQ(group.storage().raw()[0u][0u], 42); for(auto entity: group) { ASSERT_EQ(std::get<0>(cgroup.get(entity)), 42); @@ -466,38 +730,60 @@ TEST(OwningGroup, Functionalities) { ASSERT_EQ(cgroup.get(entity), '2'); } - ASSERT_EQ(*(group.data() + 0), e1); - - ASSERT_EQ(*(group.data() + 0), e1); - ASSERT_EQ(*(group.data() + 0), e0); - ASSERT_EQ(*(cgroup.data() + 1), e1); - - ASSERT_EQ(*(group.raw() + 0), 42); - ASSERT_EQ(*(group.raw() + 0), '1'); - ASSERT_EQ(*(cgroup.raw() + 1), '2'); + ASSERT_EQ(group.storage().raw()[0u][0u], 42); - registry.remove(e0); - registry.remove(e1); + registry.erase(e0); + registry.erase(e1); ASSERT_EQ(group.begin(), group.end()); ASSERT_EQ(cgroup.begin(), cgroup.end()); + ASSERT_EQ(group.rbegin(), group.rend()); + ASSERT_EQ(cgroup.rbegin(), cgroup.rend()); + ASSERT_TRUE(group.empty()); + + decltype(group) invalid{}; + + ASSERT_TRUE(group); + ASSERT_TRUE(cgroup); + ASSERT_FALSE(invalid); +} + +TEST(OwningGroup, Invalid) { + entt::registry registry{}; + auto group = std::as_const(registry).group_if_exists(entt::get); + + const auto entity = registry.create(); + registry.emplace(entity); + registry.emplace(entity); + + ASSERT_FALSE(group); + ASSERT_TRUE(group.empty()); + ASSERT_EQ(group.size(), 0u); + + ASSERT_EQ(group.begin(), group.end()); + ASSERT_EQ(group.rbegin(), group.rend()); + + ASSERT_FALSE(group.contains(entity)); + ASSERT_EQ(group.find(entity), group.end()); + ASSERT_EQ(group.front(), entt::entity{entt::null}); + ASSERT_EQ(group.back(), entt::entity{entt::null}); } TEST(OwningGroup, ElementAccess) { entt::registry registry; auto group = registry.group(entt::get); - auto cgroup = std::as_const(registry).group(entt::get); + auto cgroup = std::as_const(registry).group_if_exists(entt::get); const auto e0 = registry.create(); - registry.assign(e0); - registry.assign(e0); + registry.emplace(e0); + registry.emplace(e0); const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(e1); + registry.emplace(e1); - for(typename decltype(group)::size_type i{}; i < group.size(); ++i) { + for(auto i = 0u; i < group.size(); ++i) { ASSERT_EQ(group[i], i ? e0 : e1); ASSERT_EQ(cgroup[i], i ? e0 : e1); } @@ -508,12 +794,12 @@ TEST(OwningGroup, Contains) { auto group = registry.group(entt::get); const auto e0 = registry.create(); - registry.assign(e0); - registry.assign(e0); + registry.emplace(e0); + registry.emplace(e0); const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(e1); + registry.emplace(e1); registry.destroy(e0); @@ -525,198 +811,254 @@ TEST(OwningGroup, Empty) { entt::registry registry; const auto e0 = registry.create(); - registry.assign(e0); - registry.assign(e0); - registry.assign(e0); + registry.emplace(e0); + registry.emplace(e0); + registry.emplace(e0); const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(e1); + registry.emplace(e1); - for(auto entity: registry.group(entt::get)) { - (void)entity; - FAIL(); - } - - for(auto entity: registry.group(entt::get)) { - (void)entity; - FAIL(); - } + ASSERT_TRUE((registry.group(entt::get).empty())); + ASSERT_TRUE((registry.group(entt::get).empty())); } TEST(OwningGroup, Each) { entt::registry registry; + entt::entity entity[2]{registry.create(), registry.create()}; + auto group = registry.group(entt::get); + auto cgroup = std::as_const(registry).group_if_exists(entt::get); - const auto e0 = registry.create(); - registry.assign(e0); - registry.assign(e0); + registry.emplace(entity[0u], 0); + registry.emplace(entity[0u], 0); - const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(entity[1u], 1); + registry.emplace(entity[1u], 1); + + auto iterable = group.each(); + auto citerable = cgroup.each(); + + ASSERT_NE(citerable.begin(), citerable.end()); + ASSERT_NO_THROW(iterable.begin()->operator=(*iterable.begin())); + ASSERT_EQ(decltype(iterable.end()){}, iterable.end()); + + auto it = iterable.begin(); - auto cgroup = std::as_const(registry).group(entt::get); - std::size_t cnt = 0; + ASSERT_EQ((it++, ++it), iterable.end()); - group.each([&cnt](auto, int &, char &) { ++cnt; }); - group.each([&cnt](int &, char &) { ++cnt; }); + group.each([expected = 1u](auto entt, int &ivalue, char &cvalue) mutable { + ASSERT_EQ(entt::to_integral(entt), expected); + ASSERT_EQ(ivalue, expected); + ASSERT_EQ(cvalue, expected); + --expected; + }); + + cgroup.each([expected = 1u](const int &ivalue, const char &cvalue) mutable { + ASSERT_EQ(ivalue, expected); + ASSERT_EQ(cvalue, expected); + --expected; + }); - ASSERT_EQ(cnt, std::size_t{4}); + ASSERT_EQ(std::get<0>(*iterable.begin()), entity[1u]); + ASSERT_EQ(std::get<0>(*++citerable.begin()), entity[0u]); - cgroup.each([&cnt](auto, const int &, const char &) { --cnt; }); - cgroup.each([&cnt](const int &, const char &) { --cnt; }); + static_assert(std::is_same_v(*iterable.begin())), int &>); + static_assert(std::is_same_v(*citerable.begin())), const char &>); - ASSERT_EQ(cnt, std::size_t{0}); + // do not use iterable, make sure an iterable group works when created from a temporary + for(auto [entt, ivalue, cvalue]: registry.group(entt::get).each()) { + ASSERT_EQ(entt::to_integral(entt), ivalue); + ASSERT_EQ(entt::to_integral(entt), cvalue); + } } TEST(OwningGroup, SortOrdered) { entt::registry registry; auto group = registry.group(); - entt::entity entities[5] = { - registry.create(), - registry.create(), - registry.create(), - registry.create(), - registry.create() - }; + entt::entity entities[5]{}; + registry.create(std::begin(entities), std::end(entities)); - registry.assign(entities[0], 12); - registry.assign(entities[0], 'a'); + registry.emplace(entities[0], 12); + registry.emplace(entities[0], 'a'); - registry.assign(entities[1], 9); - registry.assign(entities[1], 'b'); + registry.emplace(entities[1], 9); + registry.emplace(entities[1], 'b'); - registry.assign(entities[2], 6); - registry.assign(entities[2], 'c'); + registry.emplace(entities[2], 6); + registry.emplace(entities[2], 'c'); - registry.assign(entities[3], 1); - registry.assign(entities[4], 2); + registry.emplace(entities[3], 1); + registry.emplace(entities[4], 2); - group.sort([&group](const auto lhs, const auto rhs) { + group.sort([&group](const entt::entity lhs, const entt::entity rhs) { return group.get(lhs).value < group.get(rhs).value; }); - ASSERT_EQ(*(group.data() + 0u), entities[0]); - ASSERT_EQ(*(group.data() + 1u), entities[1]); - ASSERT_EQ(*(group.data() + 2u), entities[2]); - ASSERT_EQ(*(group.data() + 3u), entities[3]); - ASSERT_EQ(*(group.data() + 4u), entities[4]); + ASSERT_EQ(group.storage().data()[0u], entities[0]); + ASSERT_EQ(group.storage().data()[1u], entities[1]); + ASSERT_EQ(group.storage().data()[2u], entities[2]); + ASSERT_EQ(group.storage().data()[3u], entities[3]); + ASSERT_EQ(group.storage().data()[4u], entities[4]); - ASSERT_EQ((group.raw() + 0u)->value, 12); - ASSERT_EQ((group.raw() + 1u)->value, 9); - ASSERT_EQ((group.raw() + 2u)->value, 6); - ASSERT_EQ((group.raw() + 3u)->value, 1); - ASSERT_EQ((group.raw() + 4u)->value, 2); + ASSERT_EQ(group.storage().raw()[0u][0u].value, 12); + ASSERT_EQ(group.storage().raw()[0u][1u].value, 9); + ASSERT_EQ(group.storage().raw()[0u][2u].value, 6); + ASSERT_EQ(group.storage().raw()[0u][3u].value, 1); + ASSERT_EQ(group.storage().raw()[0u][4u].value, 2); - ASSERT_EQ(*(group.raw() + 0u), 'a'); - ASSERT_EQ(*(group.raw() + 1u), 'b'); - ASSERT_EQ(*(group.raw() + 2u), 'c'); + ASSERT_EQ(group.storage().raw()[0u][0u], 'a'); + ASSERT_EQ(group.storage().raw()[0u][1u], 'b'); + ASSERT_EQ(group.storage().raw()[0u][2u], 'c'); + + ASSERT_EQ((group.get(entities[0])), (std::make_tuple(boxed_int{12}, 'a'))); + ASSERT_EQ((group.get(entities[1])), (std::make_tuple(boxed_int{9}, 'b'))); + ASSERT_EQ((group.get(entities[2])), (std::make_tuple(boxed_int{6}, 'c'))); + + ASSERT_FALSE(group.contains(entities[3])); + ASSERT_FALSE(group.contains(entities[4])); } TEST(OwningGroup, SortReverse) { entt::registry registry; auto group = registry.group(); - entt::entity entities[5] = { - registry.create(), - registry.create(), - registry.create(), - registry.create(), - registry.create() - }; + entt::entity entities[5]{}; + registry.create(std::begin(entities), std::end(entities)); - registry.assign(entities[0], 6); - registry.assign(entities[0], 'a'); + registry.emplace(entities[0], 6); + registry.emplace(entities[0], 'a'); - registry.assign(entities[1], 9); - registry.assign(entities[1], 'b'); + registry.emplace(entities[1], 9); + registry.emplace(entities[1], 'b'); - registry.assign(entities[2], 12); - registry.assign(entities[2], 'c'); + registry.emplace(entities[2], 12); + registry.emplace(entities[2], 'c'); - registry.assign(entities[3], 1); - registry.assign(entities[4], 2); + registry.emplace(entities[3], 1); + registry.emplace(entities[4], 2); group.sort([](const auto &lhs, const auto &rhs) { return lhs.value < rhs.value; }); - ASSERT_EQ(*(group.data() + 0u), entities[2]); - ASSERT_EQ(*(group.data() + 1u), entities[1]); - ASSERT_EQ(*(group.data() + 2u), entities[0]); - ASSERT_EQ(*(group.data() + 3u), entities[3]); - ASSERT_EQ(*(group.data() + 4u), entities[4]); + ASSERT_EQ(group.storage().data()[0u], entities[2]); + ASSERT_EQ(group.storage().data()[1u], entities[1]); + ASSERT_EQ(group.storage().data()[2u], entities[0]); + ASSERT_EQ(group.storage().data()[3u], entities[3]); + ASSERT_EQ(group.storage().data()[4u], entities[4]); + + ASSERT_EQ(group.storage().raw()[0u][0u].value, 12); + ASSERT_EQ(group.storage().raw()[0u][1u].value, 9); + ASSERT_EQ(group.storage().raw()[0u][2u].value, 6); + ASSERT_EQ(group.storage().raw()[0u][3u].value, 1); + ASSERT_EQ(group.storage().raw()[0u][4u].value, 2); - ASSERT_EQ((group.raw() + 0u)->value, 12); - ASSERT_EQ((group.raw() + 1u)->value, 9); - ASSERT_EQ((group.raw() + 2u)->value, 6); - ASSERT_EQ((group.raw() + 3u)->value, 1); - ASSERT_EQ((group.raw() + 4u)->value, 2); + ASSERT_EQ(group.storage().raw()[0u][0u], 'c'); + ASSERT_EQ(group.storage().raw()[0u][1u], 'b'); + ASSERT_EQ(group.storage().raw()[0u][2u], 'a'); - ASSERT_EQ(*(group.raw() + 0u), 'c'); - ASSERT_EQ(*(group.raw() + 1u), 'b'); - ASSERT_EQ(*(group.raw() + 2u), 'a'); + ASSERT_EQ((group.get(entities[0])), (std::make_tuple(boxed_int{6}, 'a'))); + ASSERT_EQ((group.get(entities[1])), (std::make_tuple(boxed_int{9}, 'b'))); + ASSERT_EQ((group.get(entities[2])), (std::make_tuple(boxed_int{12}, 'c'))); + + ASSERT_FALSE(group.contains(entities[3])); + ASSERT_FALSE(group.contains(entities[4])); } TEST(OwningGroup, SortUnordered) { entt::registry registry; auto group = registry.group(entt::get); - entt::entity entities[7] = { - registry.create(), - registry.create(), - registry.create(), - registry.create(), - registry.create(), - registry.create(), - registry.create() - }; + entt::entity entities[7]{}; + registry.create(std::begin(entities), std::end(entities)); - registry.assign(entities[0], 6); - registry.assign(entities[0], 'c'); + registry.emplace(entities[0], 6); + registry.emplace(entities[0], 'c'); - registry.assign(entities[1], 3); - registry.assign(entities[1], 'b'); + registry.emplace(entities[1], 3); + registry.emplace(entities[1], 'b'); - registry.assign(entities[2], 1); - registry.assign(entities[2], 'a'); + registry.emplace(entities[2], 1); + registry.emplace(entities[2], 'a'); - registry.assign(entities[3], 9); - registry.assign(entities[3], 'd'); + registry.emplace(entities[3], 9); + registry.emplace(entities[3], 'd'); - registry.assign(entities[4], 12); - registry.assign(entities[4], 'e'); + registry.emplace(entities[4], 12); + registry.emplace(entities[4], 'e'); - registry.assign(entities[5], 4); - registry.assign(entities[6], 5); + registry.emplace(entities[5], 4); + registry.emplace(entities[6], 5); - group.sort([](const auto lhs, const auto rhs) { + group.sort([](const auto lhs, const auto rhs) { + static_assert(std::is_same_v(lhs)), boxed_int &>); + static_assert(std::is_same_v(rhs)), char &>); + return std::get<1>(lhs) < std::get<1>(rhs); + }); + + ASSERT_EQ(group.storage().data()[0u], entities[4]); + ASSERT_EQ(group.storage().data()[1u], entities[3]); + ASSERT_EQ(group.storage().data()[2u], entities[0]); + ASSERT_EQ(group.storage().data()[3u], entities[1]); + ASSERT_EQ(group.storage().data()[4u], entities[2]); + ASSERT_EQ(group.storage().data()[5u], entities[5]); + ASSERT_EQ(group.storage().data()[6u], entities[6]); + + ASSERT_EQ(group.storage().raw()[0u][0u].value, 12); + ASSERT_EQ(group.storage().raw()[0u][1u].value, 9); + ASSERT_EQ(group.storage().raw()[0u][2u].value, 6); + ASSERT_EQ(group.storage().raw()[0u][3u].value, 3); + ASSERT_EQ(group.storage().raw()[0u][4u].value, 1); + ASSERT_EQ(group.storage().raw()[0u][5u].value, 4); + ASSERT_EQ(group.storage().raw()[0u][6u].value, 5); + + ASSERT_EQ(group.get(group.storage().data()[0u]), 'e'); + ASSERT_EQ(group.get(group.storage().data()[1u]), 'd'); + ASSERT_EQ(group.get(group.storage().data()[2u]), 'c'); + ASSERT_EQ(group.get(group.storage().data()[3u]), 'b'); + ASSERT_EQ(group.get(group.storage().data()[4u]), 'a'); + + ASSERT_FALSE(group.contains(entities[5])); + ASSERT_FALSE(group.contains(entities[6])); +} + +TEST(OwningGroup, SortWithExclusionList) { + entt::registry registry; + auto group = registry.group(entt::exclude); + + entt::entity entities[5]{}; + registry.create(std::begin(entities), std::end(entities)); + + registry.emplace(entities[0], 0); + registry.emplace(entities[1], 1); + registry.emplace(entities[2], 2); + registry.emplace(entities[3], 3); + registry.emplace(entities[4], 4); + + registry.emplace(entities[2]); + + group.sort([](const entt::entity lhs, const entt::entity rhs) { return lhs < rhs; }); - ASSERT_EQ(*(group.data() + 0u), entities[4]); - ASSERT_EQ(*(group.data() + 1u), entities[3]); - ASSERT_EQ(*(group.data() + 2u), entities[0]); - ASSERT_EQ(*(group.data() + 3u), entities[1]); - ASSERT_EQ(*(group.data() + 4u), entities[2]); - ASSERT_EQ(*(group.data() + 5u), entities[5]); - ASSERT_EQ(*(group.data() + 6u), entities[6]); - - ASSERT_EQ((group.raw() + 0u)->value, 12); - ASSERT_EQ((group.raw() + 1u)->value, 9); - ASSERT_EQ((group.raw() + 2u)->value, 6); - ASSERT_EQ((group.raw() + 3u)->value, 3); - ASSERT_EQ((group.raw() + 4u)->value, 1); - ASSERT_EQ((group.raw() + 5u)->value, 4); - ASSERT_EQ((group.raw() + 6u)->value, 5); - - ASSERT_EQ(*(group.raw() + 0u), 'c'); - ASSERT_EQ(*(group.raw() + 1u), 'b'); - ASSERT_EQ(*(group.raw() + 2u), 'a'); - ASSERT_EQ(*(group.raw() + 3u), 'd'); - ASSERT_EQ(*(group.raw() + 4u), 'e'); + ASSERT_EQ(group.storage().data()[0u], entities[4]); + ASSERT_EQ(group.storage().data()[1u], entities[3]); + ASSERT_EQ(group.storage().data()[2u], entities[1]); + ASSERT_EQ(group.storage().data()[3u], entities[0]); + + ASSERT_EQ(group.storage().raw()[0u][0u].value, 4); + ASSERT_EQ(group.storage().raw()[0u][1u].value, 3); + ASSERT_EQ(group.storage().raw()[0u][2u].value, 1); + ASSERT_EQ(group.storage().raw()[0u][3u].value, 0); + + ASSERT_EQ(group.get(entities[0]).value, 0); + ASSERT_EQ(group.get(entities[1]).value, 1); + ASSERT_EQ(group.get(entities[3]).value, 3); + ASSERT_EQ(group.get(entities[4]).value, 4); + + ASSERT_FALSE(group.contains(entities[2])); } TEST(OwningGroup, IndexRebuiltOnDestroy) { @@ -726,16 +1068,16 @@ TEST(OwningGroup, IndexRebuiltOnDestroy) { const auto e0 = registry.create(); const auto e1 = registry.create(); - registry.assign(e0, 0u); - registry.assign(e1, 1u); + registry.emplace(e0, 0u); + registry.emplace(e1, 1u); - registry.assign(e0, 0); - registry.assign(e1, 1); + registry.emplace(e0, 0); + registry.emplace(e1, 1); registry.destroy(e0); - registry.assign(registry.create(), 42); + registry.emplace(registry.create(), 42); - ASSERT_EQ(group.size(), typename decltype(group)::size_type{1}); + ASSERT_EQ(group.size(), 1u); ASSERT_EQ(group[{}], e1); ASSERT_EQ(group.get(e1), 1); ASSERT_EQ(group.get(e1), 1u); @@ -745,38 +1087,54 @@ TEST(OwningGroup, IndexRebuiltOnDestroy) { ASSERT_EQ(ivalue, 1); ASSERT_EQ(uivalue, 1u); }); + + for(auto &&curr: group.each()) { + ASSERT_EQ(std::get<0>(curr), e1); + ASSERT_EQ(std::get<1>(curr), 1); + ASSERT_EQ(std::get<2>(curr), 1u); + } } TEST(OwningGroup, ConstNonConstAndAllInBetween) { entt::registry registry; - auto group = registry.group(entt::get); + auto group = registry.group(entt::get); - ASSERT_EQ(group.size(), decltype(group.size()){0}); + ASSERT_EQ(group.size(), 0u); const auto entity = registry.create(); - registry.assign(entity, 0); - registry.assign(entity, 'c'); - registry.assign(entity, 0.); - registry.assign(entity, 0.f); - - ASSERT_EQ(group.size(), decltype(group.size()){1}); - - ASSERT_TRUE((std::is_same_v(0)), int &>)); - ASSERT_TRUE((std::is_same_v(0)), const char &>)); - ASSERT_TRUE((std::is_same_v(0)), double &>)); - ASSERT_TRUE((std::is_same_v(0)), const float &>)); - ASSERT_TRUE((std::is_same_v(0)), std::tuple>)); - ASSERT_TRUE((std::is_same_v()), const float *>)); - ASSERT_TRUE((std::is_same_v()), double *>)); - ASSERT_TRUE((std::is_same_v()), const char *>)); - ASSERT_TRUE((std::is_same_v()), int *>)); - - group.each([](auto, auto &&i, auto &&c, auto &&d, auto &&f) { - ASSERT_TRUE((std::is_same_v)); - ASSERT_TRUE((std::is_same_v)); - ASSERT_TRUE((std::is_same_v)); - ASSERT_TRUE((std::is_same_v)); + registry.emplace(entity, 0); + registry.emplace(entity, 'c'); + registry.emplace(entity); + registry.emplace(entity, 0.); + registry.emplace(entity, 0.f); + + ASSERT_EQ(group.size(), 1u); + + static_assert(std::is_same_v({})), int &>); + static_assert(std::is_same_v({})), const char &>); + static_assert(std::is_same_v({})), double &>); + static_assert(std::is_same_v({})), const float &>); + static_assert(std::is_same_v({})), std::tuple>); + static_assert(std::is_same_v>); + + static_assert(std::is_same_v(entt::get)), decltype(std::as_const(registry).group_if_exists(entt::get))>); + static_assert(std::is_same_v(entt::get)), decltype(std::as_const(registry).group_if_exists(entt::get))>); + static_assert(std::is_same_v(entt::get)), decltype(std::as_const(registry).group_if_exists(entt::get))>); + + group.each([](auto &&i, auto &&c, auto &&d, auto &&f) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); }); + + for(auto [entt, iv, cv, dv, fv]: group.each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } } TEST(OwningGroup, Find) { @@ -784,22 +1142,22 @@ TEST(OwningGroup, Find) { auto group = registry.group(entt::get); const auto e0 = registry.create(); - registry.assign(e0); - registry.assign(e0); + registry.emplace(e0); + registry.emplace(e0); const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(e1); + registry.emplace(e1); const auto e2 = registry.create(); - registry.assign(e2); - registry.assign(e2); + registry.emplace(e2); + registry.emplace(e2); const auto e3 = registry.create(); - registry.assign(e3); - registry.assign(e3); + registry.emplace(e3); + registry.emplace(e3); - registry.remove(e1); + registry.erase(e1); ASSERT_NE(group.find(e0), group.end()); ASSERT_EQ(group.find(e1), group.end()); @@ -817,8 +1175,8 @@ TEST(OwningGroup, Find) { const auto e4 = registry.create(); registry.destroy(e4); const auto e5 = registry.create(); - registry.assign(e5); - registry.assign(e5); + registry.emplace(e5); + registry.emplace(e5); ASSERT_NE(group.find(e5), group.end()); ASSERT_EQ(group.find(e4), group.end()); @@ -828,112 +1186,283 @@ TEST(OwningGroup, ExcludedComponents) { entt::registry registry; const auto e0 = registry.create(); - registry.assign(e0, 0); + registry.emplace(e0, 0); const auto e1 = registry.create(); - registry.assign(e1, 1); - registry.assign(e1); + registry.emplace(e1, 1); + registry.emplace(e1); const auto group = registry.group(entt::exclude); const auto e2 = registry.create(); - registry.assign(e2, 2); + registry.emplace(e2, 2); const auto e3 = registry.create(); - registry.assign(e3, 3); - registry.assign(e3); + registry.emplace(e3, 3); + registry.emplace(e3); for(const auto entity: group) { + ASSERT_TRUE(entity == e0 || entity == e2); + if(entity == e0) { ASSERT_EQ(group.get(e0), 0); } else if(entity == e2) { ASSERT_EQ(group.get(e2), 2); - } else { - FAIL(); } } - registry.assign(e0); - registry.assign(e2); + registry.emplace(e0); + registry.emplace(e2); ASSERT_TRUE(group.empty()); - registry.remove(e1); - registry.remove(e3); + registry.erase(e1); + registry.erase(e3); for(const auto entity: group) { + ASSERT_TRUE(entity == e1 || entity == e3); + if(entity == e1) { ASSERT_EQ(group.get(e1), 1); } else if(entity == e3) { ASSERT_EQ(group.get(e3), 3); - } else { - FAIL(); } } } TEST(OwningGroup, EmptyAndNonEmptyTypes) { entt::registry registry; - const auto group = registry.group(entt::get); + const auto group = registry.group(entt::get); const auto e0 = registry.create(); - registry.assign(e0); - registry.assign(e0); + registry.emplace(e0); + registry.emplace(e0); const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(e1); + registry.emplace(e1); - registry.assign(registry.create()); + registry.emplace(registry.create()); for(const auto entity: group) { ASSERT_TRUE(entity == e0 || entity == e1); } - group.each([e0, e1](const auto entity, empty_type, const int &) { + group.each([e0, e1](const auto entity, const int &) { ASSERT_TRUE(entity == e0 || entity == e1); }); - ASSERT_EQ(group.size(), typename decltype(group)::size_type{2}); + for(auto [entt, iv]: group.each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_TRUE(entt == e0 || entt == e1); + } + + ASSERT_EQ(group.size(), 2u); } TEST(OwningGroup, TrackEntitiesOnComponentDestruction) { entt::registry registry; const auto group = registry.group(entt::exclude); - const auto cgroup = std::as_const(registry).group(entt::exclude); + const auto cgroup = std::as_const(registry).group_if_exists(entt::exclude); const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); + registry.emplace(entity); + registry.emplace(entity); ASSERT_TRUE(group.empty()); ASSERT_TRUE(cgroup.empty()); - registry.remove(entity); + registry.erase(entity); ASSERT_FALSE(group.empty()); ASSERT_FALSE(cgroup.empty()); } -TEST(OwningGroup, Less) { +TEST(OwningGroup, EmptyTypes) { entt::registry registry; - const auto entity = std::get<0>(registry.create>()); - registry.create(); + const auto entity = registry.create(); - registry.group(entt::get>).less([entity](const auto entt, int, char) { + registry.emplace(entity); + registry.emplace(entity); + registry.emplace(entity); + + registry.group(entt::get).each([entity](const auto entt, int, char) { ASSERT_EQ(entity, entt); }); - registry.group(entt::get, int>).less([check = true](int, char) mutable { + for(auto [entt, iv, cv]: registry.group(entt::get).each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_EQ(entity, entt); + } + + registry.group(entt::get).each([check = true](char, int) mutable { ASSERT_TRUE(check); check = false; }); - registry.group>(entt::get).less([entity](const auto entt, int, char) { + for(auto [entt, cv, iv]: registry.group(entt::get).each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_EQ(entity, entt); + } + + registry.group(entt::get).each([entity](const auto entt, int, char) { ASSERT_EQ(entity, entt); }); - registry.group(entt::get).less([entity](const auto entt, int, char, double) { + for(auto [entt, iv, cv]: registry.group(entt::get).each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); ASSERT_EQ(entity, entt); + } + + auto iterable = registry.group(entt::get).each(); + + ASSERT_EQ(iterable.begin(), iterable.end()); +} + +TEST(OwningGroup, FrontBack) { + entt::registry registry; + auto group = registry.group(entt::get); + + ASSERT_EQ(group.front(), static_cast(entt::null)); + ASSERT_EQ(group.back(), static_cast(entt::null)); + + const auto e0 = registry.create(); + registry.emplace(e0); + registry.emplace(e0); + + const auto e1 = registry.create(); + registry.emplace(e1); + registry.emplace(e1); + + const auto entity = registry.create(); + registry.emplace(entity); + + ASSERT_EQ(group.front(), e1); + ASSERT_EQ(group.back(), e0); +} + +TEST(OwningGroup, SignalRace) { + entt::registry registry; + registry.on_construct().connect<&entt::registry::emplace_or_replace>(); + const auto group = registry.group(entt::get); + + auto entity = registry.create(); + registry.emplace(entity); + + ASSERT_EQ(group.size(), 1u); +} + +TEST(OwningGroup, StableLateInitialization) { + entt::registry registry; + + for(std::size_t i{}; i < 30u; ++i) { + auto entity = registry.create(); + if(!(i % 2u)) registry.emplace(entity); + if(!(i % 3u)) registry.emplace(entity); + } + + // thanks to @pgruenbacher for pointing out this corner case + ASSERT_EQ((registry.group().size()), 5u); +} + +TEST(OwningGroup, PreventEarlyOptOut) { + entt::registry registry; + + registry.emplace(registry.create(), 3); + + const auto entity = registry.create(); + registry.emplace(entity, 'c'); + registry.emplace(entity, 2); + + // thanks to @pgruenbacher for pointing out this corner case + registry.group().each([entity](const auto entt, const auto &c, const auto &i) { + ASSERT_EQ(entity, entt); + ASSERT_EQ(c, 'c'); + ASSERT_EQ(i, 2); + }); +} + +TEST(OwningGroup, SwappingValuesIsAllowed) { + entt::registry registry; + const auto group = registry.group(entt::get); + + for(std::size_t i{}; i < 2u; ++i) { + const auto entity = registry.create(); + registry.emplace(entity, static_cast(i)); + registry.emplace(entity); + } + + registry.destroy(group.back()); + + // thanks to @andranik3949 for pointing out this missing test + registry.view().each([](const auto entity, const auto &value) { + ASSERT_EQ(entt::to_integral(entity), value.value); }); } + +TEST(OwningGroup, ExtendedGet) { + using type = decltype(std::declval().group(entt::get).get({})); + + static_assert(std::tuple_size_v == 2u); + static_assert(std::is_same_v, int &>); + static_assert(std::is_same_v, char &>); + + entt::registry registry; + const auto entity = registry.create(); + + registry.emplace(entity, 42); + registry.emplace(entity, 'c'); + + const auto tup = registry.group(entt::get).get(entity); + + ASSERT_EQ(std::get<0>(tup), 42); + ASSERT_EQ(std::get<1>(tup), 'c'); +} + +TEST(OwningGroup, IterableGroupAlgorithmCompatibility) { + entt::registry registry; + const auto entity = registry.create(); + + registry.emplace(entity); + registry.emplace(entity); + + const auto group = registry.group(entt::get); + const auto iterable = group.each(); + const auto it = std::find_if(iterable.begin(), iterable.end(), [entity](auto args) { return std::get<0>(args) == entity; }); + + ASSERT_EQ(std::get<0>(*it), entity); +} + +TEST(OwningGroup, Storage) { + entt::registry registry; + const auto entity = registry.create(); + const auto group = registry.group(entt::get); + + static_assert(std::is_same_v()), typename entt::storage_traits::storage_type &>); + static_assert(std::is_same_v()), typename entt::storage_traits::storage_type &>); + static_assert(std::is_same_v()), const typename entt::storage_traits::storage_type &>); + static_assert(std::is_same_v()), const typename entt::storage_traits::storage_type &>); + + ASSERT_EQ(group.size(), 0u); + + group.storage().emplace(entity); + registry.emplace(entity); + + ASSERT_EQ(group.size(), 1u); + ASSERT_TRUE(group.storage().contains(entity)); + ASSERT_TRUE(group.storage().contains(entity)); + ASSERT_TRUE((registry.all_of(entity))); + + group.storage<0u>().erase(entity); + + ASSERT_EQ(group.size(), 0u); + ASSERT_TRUE(group.storage<1u>().contains(entity)); + ASSERT_FALSE((registry.all_of(entity))); +} diff --git a/modules/entt/test/entt/entity/handle.cpp b/modules/entt/test/entt/entity/handle.cpp new file mode 100644 index 0000000..7e37d45 --- /dev/null +++ b/modules/entt/test/entt/entity/handle.cpp @@ -0,0 +1,243 @@ +#include +#include +#include +#include +#include +#include +#include + +TEST(BasicHandle, Assumptions) { + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_assignable_v); + static_assert(std::is_trivially_destructible_v); + + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_assignable_v); + static_assert(std::is_trivially_destructible_v); +} + +TEST(BasicHandle, DeductionGuide) { + static_assert(std::is_same_v(), {}}), entt::basic_handle>); + static_assert(std::is_same_v(), {}}), entt::basic_handle>); +} + +TEST(BasicHandle, Construction) { + entt::registry registry; + const auto entity = registry.create(); + + entt::handle handle{registry, entity}; + entt::const_handle chandle{std::as_const(registry), entity}; + + ASSERT_FALSE(entt::null == handle.entity()); + ASSERT_EQ(entity, handle); + ASSERT_TRUE(handle); + + ASSERT_FALSE(entt::null == chandle.entity()); + ASSERT_EQ(entity, chandle); + ASSERT_TRUE(chandle); + + ASSERT_EQ(handle, chandle); + + static_assert(std::is_same_v); + static_assert(std::is_same_v); +} + +TEST(BasicHandle, Invalidation) { + entt::handle handle; + + ASSERT_FALSE(handle); + ASSERT_EQ(handle.registry(), nullptr); + ASSERT_EQ(handle.entity(), entt::entity{entt::null}); + + entt::registry registry; + const auto entity = registry.create(); + + handle = {registry, entity}; + + ASSERT_TRUE(handle); + ASSERT_NE(handle.registry(), nullptr); + ASSERT_NE(handle.entity(), entt::entity{entt::null}); + + handle = {}; + + ASSERT_FALSE(handle); + ASSERT_EQ(handle.registry(), nullptr); + ASSERT_EQ(handle.entity(), entt::entity{entt::null}); +} + +TEST(BasicHandle, Destruction) { + using traits_type = entt::entt_traits; + + entt::registry registry; + const auto entity = registry.create(); + entt::handle handle{registry, entity}; + + ASSERT_TRUE(handle); + ASSERT_TRUE(handle.valid()); + ASSERT_NE(handle.registry(), nullptr); + ASSERT_EQ(handle.entity(), entity); + + handle.destroy(traits_type::to_version(entity)); + + ASSERT_FALSE(handle); + ASSERT_FALSE(handle.valid()); + ASSERT_NE(handle.registry(), nullptr); + ASSERT_EQ(handle.entity(), entity); + ASSERT_EQ(registry.current(entity), typename entt::registry::version_type{}); + + handle = entt::handle{registry, registry.create()}; + + ASSERT_TRUE(handle); + ASSERT_TRUE(handle.valid()); + ASSERT_NE(handle.registry(), nullptr); + ASSERT_EQ(handle.entity(), entity); + + handle.destroy(); + + ASSERT_FALSE(handle); + ASSERT_FALSE(handle.valid()); + ASSERT_NE(handle.registry(), nullptr); + ASSERT_EQ(handle.entity(), entity); + ASSERT_NE(registry.current(entity), typename entt::registry::version_type{}); +} + +TEST(BasicHandle, Comparison) { + entt::registry registry; + const auto entity = registry.create(); + + entt::handle handle{registry, entity}; + entt::const_handle chandle = handle; + + ASSERT_NE(handle, entt::handle{}); + ASSERT_FALSE(handle == entt::handle{}); + ASSERT_TRUE(handle != entt::handle{}); + + ASSERT_NE(chandle, entt::const_handle{}); + ASSERT_FALSE(chandle == entt::const_handle{}); + ASSERT_TRUE(chandle != entt::const_handle{}); + + ASSERT_EQ(handle, chandle); + ASSERT_TRUE(handle == chandle); + ASSERT_FALSE(handle != chandle); + + ASSERT_EQ(entt::handle{}, entt::const_handle{}); + ASSERT_TRUE(entt::handle{} == entt::const_handle{}); + ASSERT_FALSE(entt::handle{} != entt::const_handle{}); + + handle = {}; + chandle = {}; + + ASSERT_EQ(handle, entt::handle{}); + ASSERT_TRUE(handle == entt::handle{}); + ASSERT_FALSE(handle != entt::handle{}); + + ASSERT_EQ(chandle, entt::const_handle{}); + ASSERT_TRUE(chandle == entt::const_handle{}); + ASSERT_FALSE(chandle != entt::const_handle{}); + + entt::registry other; + const auto entt = other.create(); + + handle = {registry, entity}; + chandle = {other, entt}; + + ASSERT_NE(handle, chandle); + ASSERT_FALSE(chandle == handle); + ASSERT_TRUE(chandle != handle); + ASSERT_EQ(handle.entity(), chandle.entity()); + ASSERT_NE(handle.registry(), chandle.registry()); +} + +TEST(BasicHandle, Component) { + entt::registry registry; + const auto entity = registry.create(); + entt::handle_view handle{registry, entity}; + + ASSERT_EQ(3, handle.emplace(3)); + ASSERT_EQ('c', handle.emplace_or_replace('c')); + ASSERT_EQ(.3, handle.emplace_or_replace(.3)); + + const auto &patched = handle.patch([](auto &comp) { comp = 42; }); + + ASSERT_EQ(42, patched); + ASSERT_EQ('a', handle.replace('a')); + ASSERT_TRUE((handle.all_of())); + ASSERT_EQ((std::make_tuple(42, 'a', .3)), (handle.get())); + + handle.erase(); + + ASSERT_TRUE(registry.storage().empty()); + ASSERT_TRUE(registry.storage().empty()); + ASSERT_EQ(0u, (handle.remove())); + + handle.visit([&handle](const auto id, const auto &pool) { + ASSERT_EQ(id, entt::type_id().hash()); + ASSERT_TRUE(pool.contains(handle.entity())); + }); + + ASSERT_TRUE((handle.any_of())); + ASSERT_FALSE((handle.all_of())); + ASSERT_FALSE(handle.orphan()); + + ASSERT_EQ(1u, (handle.remove())); + ASSERT_TRUE(registry.storage().empty()); + ASSERT_TRUE(handle.orphan()); + + ASSERT_EQ(42, handle.get_or_emplace(42)); + ASSERT_EQ(42, handle.get_or_emplace(1)); + ASSERT_EQ(42, handle.get()); + + ASSERT_EQ(42, *handle.try_get()); + ASSERT_EQ(nullptr, handle.try_get()); + ASSERT_EQ(nullptr, std::get<1>(handle.try_get())); +} + +TEST(BasicHandle, FromEntity) { + entt::registry registry; + const auto entity = registry.create(); + + registry.emplace(entity, 42); + registry.emplace(entity, 'c'); + + entt::handle handle{registry, entity}; + + ASSERT_TRUE(handle); + ASSERT_EQ(entity, handle.entity()); + ASSERT_TRUE((handle.all_of())); + ASSERT_EQ(handle.get(), 42); + ASSERT_EQ(handle.get(), 'c'); +} + +TEST(BasicHandle, Lifetime) { + entt::registry registry; + const auto entity = registry.create(); + auto *handle = new entt::handle{registry, entity}; + handle->emplace(); + + ASSERT_FALSE(registry.storage().empty()); + ASSERT_FALSE(registry.empty()); + + registry.each([handle](const auto e) { + ASSERT_EQ(handle->entity(), e); + }); + + delete handle; + + ASSERT_FALSE(registry.storage().empty()); + ASSERT_FALSE(registry.empty()); +} + +TEST(BasicHandle, ImplicitConversions) { + entt::registry registry; + const entt::handle handle{registry, registry.create()}; + const entt::const_handle chandle = handle; + const entt::handle_view vhandle = handle; + const entt::const_handle_view cvhandle = vhandle; + + handle.emplace(42); + + ASSERT_EQ(handle.get(), chandle.get()); + ASSERT_EQ(chandle.get(), vhandle.get()); + ASSERT_EQ(vhandle.get(), cvhandle.get()); + ASSERT_EQ(cvhandle.get(), 42); +} diff --git a/modules/entt/test/entt/entity/helper.cpp b/modules/entt/test/entt/entity/helper.cpp index a75b586..8feb999 100644 --- a/modules/entt/test/entt/entity/helper.cpp +++ b/modules/entt/test/entt/entity/helper.cpp @@ -1,106 +1,124 @@ #include -#include +#include #include #include -#include + +struct clazz { + void func(entt::registry &, entt::entity curr) { + entt = curr; + } + + entt::entity entt{entt::null}; +}; + +struct stable_type { + static constexpr auto in_place_delete = true; + int value; +}; TEST(Helper, AsView) { entt::registry registry; const entt::registry cregistry; - ([](entt::view) {})(entt::as_view{registry}); - ([](entt::view) {})(entt::as_view{registry}); - ([](entt::view) {})(entt::as_view{cregistry}); + ([](entt::view>) {})(entt::as_view{registry}); + ([](entt::view, entt::exclude_t>) {})(entt::as_view{registry}); + ([](entt::view, entt::exclude_t>) {})(entt::as_view{registry}); + ([](entt::view, entt::exclude_t>) {})(entt::as_view{cregistry}); } TEST(Helper, AsGroup) { entt::registry registry; const entt::registry cregistry; - ([](entt::group, double, float>) {})(entt::as_group{registry}); - ([](entt::group, const double, float>) {})(entt::as_group{registry}); - ([](entt::group, const double, const float>) {})(entt::as_group{cregistry}); + ([](entt::group, entt::get_t, entt::exclude_t>) {})(entt::as_group{registry}); + ([](entt::group, entt::get_t, entt::exclude_t>) {})(entt::as_group{registry}); + ([](entt::group, entt::get_t, entt::exclude_t>) {})(entt::as_group{cregistry}); } -TEST(Helper, Dependency) { +TEST(Helper, Invoke) { entt::registry registry; const auto entity = registry.create(); - entt::connect(registry.on_construct()); - ASSERT_FALSE(registry.has(entity)); - ASSERT_FALSE(registry.has(entity)); + registry.on_construct().connect>(); + registry.emplace(entity); - registry.assign(entity); + ASSERT_EQ(entity, registry.get(entity).entt); +} - ASSERT_FALSE(registry.has(entity)); - ASSERT_FALSE(registry.has(entity)); +TEST(Helper, ToEntity) { + entt::registry registry; + const entt::entity null = entt::null; + const int value = 42; - registry.assign(entity); + ASSERT_EQ(entt::to_entity(registry, 42), null); + ASSERT_EQ(entt::to_entity(registry, value), null); - ASSERT_TRUE(registry.has(entity)); - ASSERT_TRUE(registry.has(entity)); - ASSERT_EQ(registry.get(entity), .0); - ASSERT_EQ(registry.get(entity), .0f); + const auto entity = registry.create(); + auto &&storage = registry.storage(); + storage.emplace(entity); - registry.get(entity) = .3; - registry.get(entity) = .1f; - registry.remove(entity); - registry.assign(entity); + while(storage.size() < (ENTT_PACKED_PAGE - 1u)) { + storage.emplace(registry.create(), value); + } - ASSERT_EQ(registry.get(entity), .3); - ASSERT_EQ(registry.get(entity), .1f); + const auto other = registry.create(); + const auto next = registry.create(); - registry.remove(entity); - registry.remove(entity); - registry.assign(entity); + registry.emplace(other); + registry.emplace(next); - ASSERT_TRUE(registry.has(entity)); - ASSERT_EQ(registry.get(entity), .3); - ASSERT_EQ(registry.get(entity), .0f); + ASSERT_EQ(entt::to_entity(registry, registry.get(entity)), entity); + ASSERT_EQ(entt::to_entity(registry, registry.get(other)), other); + ASSERT_EQ(entt::to_entity(registry, registry.get(next)), next); - registry.remove(entity); - registry.remove(entity); - registry.remove(entity); - entt::disconnect(registry.on_construct()); - registry.assign(entity); + ASSERT_EQ(®istry.get(entity) + ENTT_PACKED_PAGE - 1u, ®istry.get(other)); - ASSERT_FALSE(registry.has(entity)); - ASSERT_FALSE(registry.has(entity)); -} + registry.destroy(other); -TEST(Dependency, MultipleListenersOnTheSameType) { - entt::registry registry; - entt::connect(registry.on_construct()); - entt::connect(registry.on_construct()); + ASSERT_EQ(entt::to_entity(registry, registry.get(entity)), entity); + ASSERT_EQ(entt::to_entity(registry, registry.get(next)), next); - const auto entity = registry.create(); - registry.assign(entity); + ASSERT_EQ(®istry.get(entity) + ENTT_PACKED_PAGE - 1u, ®istry.get(next)); - ASSERT_TRUE(registry.has(entity)); - ASSERT_TRUE(registry.has(entity)); + ASSERT_EQ(entt::to_entity(registry, 42), null); + ASSERT_EQ(entt::to_entity(registry, value), null); } -TEST(Helper, Tag) { +TEST(Helper, ToEntityStableType) { entt::registry registry; - const auto entity = registry.create(); - registry.assign>(entity); - registry.assign(entity, 42); - int counter{}; + const entt::entity null = entt::null; + const stable_type value{42}; - ASSERT_FALSE(registry.has>(entity)); - ASSERT_TRUE(registry.has>(entity)); + ASSERT_EQ(entt::to_entity(registry, stable_type{42}), null); + ASSERT_EQ(entt::to_entity(registry, value), null); - for(auto entt: registry.view>()) { - (void)entt; - ++counter; + const auto entity = registry.create(); + auto &&storage = registry.storage(); + registry.emplace(entity); + + while(storage.size() < (ENTT_PACKED_PAGE - 2u)) { + storage.emplace(registry.create(), value); } - ASSERT_NE(counter, 0); + const auto other = registry.create(); + const auto next = registry.create(); - for(auto entt: registry.view>()) { - (void)entt; - --counter; - } + registry.emplace(other); + registry.emplace(next); + + ASSERT_EQ(entt::to_entity(registry, registry.get(entity)), entity); + ASSERT_EQ(entt::to_entity(registry, registry.get(other)), other); + ASSERT_EQ(entt::to_entity(registry, registry.get(next)), next); + + ASSERT_EQ(®istry.get(entity) + ENTT_PACKED_PAGE - 2u, ®istry.get(other)); + + registry.destroy(other); + + ASSERT_EQ(entt::to_entity(registry, registry.get(entity)), entity); + ASSERT_EQ(entt::to_entity(registry, registry.get(next)), next); + + ASSERT_EQ(®istry.get(entity) + ENTT_PACKED_PAGE - 1u, ®istry.get(next)); - ASSERT_EQ(counter, 0); + ASSERT_EQ(entt::to_entity(registry, stable_type{42}), null); + ASSERT_EQ(entt::to_entity(registry, value), null); } diff --git a/modules/entt/test/entt/entity/observer.cpp b/modules/entt/test/entt/entity/observer.cpp new file mode 100644 index 0000000..fba7c93 --- /dev/null +++ b/modules/entt/test/entt/entity/observer.cpp @@ -0,0 +1,371 @@ +#include +#include +#include +#include + +TEST(Observer, Functionalities) { + entt::registry registry; + entt::observer observer{registry, entt::collector.group()}; + + ASSERT_EQ(observer.size(), 0u); + ASSERT_TRUE(observer.empty()); + ASSERT_EQ(observer.begin(), observer.end()); + + const auto entity = registry.create(); + registry.emplace(entity); + + ASSERT_EQ(observer.size(), 1u); + ASSERT_FALSE(observer.empty()); + ASSERT_EQ(*observer.data(), entity); + ASSERT_NE(observer.begin(), observer.end()); + ASSERT_EQ(++observer.begin(), observer.end()); + ASSERT_EQ(*observer.begin(), entity); + + observer.clear(); + + ASSERT_EQ(observer.size(), 0u); + ASSERT_TRUE(observer.empty()); + + observer.disconnect(); + registry.erase(entity); + registry.emplace(entity); + + ASSERT_EQ(observer.size(), 0u); + ASSERT_TRUE(observer.empty()); +} + +TEST(Observer, AllOf) { + constexpr auto collector = + entt::collector + .group(entt::exclude) + .group(); + + entt::registry registry; + entt::observer observer{registry, collector}; + const auto entity = registry.create(); + + ASSERT_TRUE(observer.empty()); + + registry.emplace(entity); + registry.emplace(entity); + + ASSERT_EQ(observer.size(), 1u); + ASSERT_FALSE(observer.empty()); + ASSERT_EQ(*observer.data(), entity); + + registry.emplace(entity); + + ASSERT_FALSE(observer.empty()); + + registry.erase(entity); + + ASSERT_TRUE(observer.empty()); + + registry.emplace(entity); + registry.emplace(entity); + + ASSERT_FALSE(observer.empty()); + + registry.erase(entity); + + ASSERT_TRUE(observer.empty()); + + registry.emplace(entity); + observer.clear(); + + ASSERT_TRUE(observer.empty()); + + observer.disconnect(); + registry.emplace_or_replace(entity); + registry.emplace_or_replace(entity); + registry.erase(entity); + + ASSERT_TRUE(observer.empty()); +} + +TEST(Observer, AllOfFiltered) { + constexpr auto collector = + entt::collector + .group() + .where(entt::exclude); + + entt::registry registry; + entt::observer observer{registry, collector}; + const auto entity = registry.create(); + + ASSERT_TRUE(observer.empty()); + + registry.emplace(entity); + + ASSERT_EQ(observer.size(), 0u); + ASSERT_TRUE(observer.empty()); + + registry.erase(entity); + registry.emplace(entity); + registry.emplace(entity); + registry.emplace(entity); + + ASSERT_TRUE(observer.empty()); + + registry.erase(entity); + registry.erase(entity); + registry.emplace(entity); + + ASSERT_EQ(observer.size(), 1u); + ASSERT_FALSE(observer.empty()); + ASSERT_EQ(*observer.data(), entity); + + registry.emplace(entity); + + ASSERT_TRUE(observer.empty()); + + registry.erase(entity); + + ASSERT_TRUE(observer.empty()); + + observer.disconnect(); + registry.erase(entity); + registry.emplace(entity); + + ASSERT_TRUE(observer.empty()); +} + +TEST(Observer, Observe) { + entt::registry registry; + entt::observer observer{registry, entt::collector.update().update()}; + const auto entity = registry.create(); + + ASSERT_TRUE(observer.empty()); + + registry.emplace(entity); + registry.emplace(entity); + + ASSERT_TRUE(observer.empty()); + + registry.emplace_or_replace(entity); + + ASSERT_EQ(observer.size(), 1u); + ASSERT_FALSE(observer.empty()); + ASSERT_EQ(*observer.data(), entity); + + observer.clear(); + registry.replace(entity); + + ASSERT_FALSE(observer.empty()); + + observer.clear(); + + ASSERT_TRUE(observer.empty()); + + observer.disconnect(); + registry.emplace_or_replace(entity); + registry.emplace_or_replace(entity); + + ASSERT_TRUE(observer.empty()); +} + +TEST(Observer, ObserveFiltered) { + constexpr auto collector = + entt::collector + .update() + .where(entt::exclude); + + entt::registry registry; + entt::observer observer{registry, collector}; + const auto entity = registry.create(); + + ASSERT_TRUE(observer.empty()); + + registry.emplace(entity); + registry.replace(entity); + + ASSERT_EQ(observer.size(), 0u); + ASSERT_TRUE(observer.empty()); + + registry.emplace(entity); + registry.emplace(entity); + registry.replace(entity); + + ASSERT_TRUE(observer.empty()); + + registry.erase(entity); + registry.replace(entity); + + ASSERT_EQ(observer.size(), 1u); + ASSERT_FALSE(observer.empty()); + ASSERT_EQ(*observer.data(), entity); + + registry.emplace(entity); + + ASSERT_TRUE(observer.empty()); + + registry.erase(entity); + + ASSERT_TRUE(observer.empty()); + + observer.disconnect(); + registry.replace(entity); + + ASSERT_TRUE(observer.empty()); +} + +TEST(Observer, AllOfObserve) { + entt::registry registry; + entt::observer observer{}; + const auto entity = registry.create(); + + observer.connect(registry, entt::collector.group().update()); + + ASSERT_TRUE(observer.empty()); + + registry.emplace(entity); + registry.emplace(entity); + registry.replace(entity); + registry.erase(entity); + + ASSERT_EQ(observer.size(), 1u); + ASSERT_FALSE(observer.empty()); + ASSERT_EQ(*observer.data(), entity); + + registry.erase(entity); + registry.emplace(entity); + + ASSERT_TRUE(observer.empty()); + + registry.replace(entity); + observer.clear(); + + ASSERT_TRUE(observer.empty()); + + observer.disconnect(); + registry.emplace_or_replace(entity); + registry.emplace_or_replace(entity); + + ASSERT_TRUE(observer.empty()); +} + +TEST(Observer, CrossRulesCornerCase) { + entt::registry registry; + entt::observer observer{registry, entt::collector.group().group()}; + const auto entity = registry.create(); + + registry.emplace(entity); + observer.clear(); + + ASSERT_TRUE(observer.empty()); + + registry.emplace(entity); + registry.erase(entity); + + ASSERT_FALSE(observer.empty()); +} + +TEST(Observer, Each) { + entt::registry registry; + entt::observer observer{registry, entt::collector.group()}; + const auto entity = registry.create(); + registry.emplace(entity); + + ASSERT_FALSE(observer.empty()); + ASSERT_EQ(observer.size(), 1u); + + std::as_const(observer).each([entity](const auto entt) { + ASSERT_EQ(entity, entt); + }); + + ASSERT_FALSE(observer.empty()); + ASSERT_EQ(observer.size(), 1u); + + observer.each([entity](const auto entt) { + ASSERT_EQ(entity, entt); + }); + + ASSERT_TRUE(observer.empty()); + ASSERT_EQ(observer.size(), 0u); +} + +TEST(Observer, MultipleFilters) { + constexpr auto collector = + entt::collector + .update() + .where() + .update() + .where(); + + entt::registry registry; + entt::observer observer{registry, collector}; + const auto entity = registry.create(); + + ASSERT_TRUE(observer.empty()); + + registry.emplace_or_replace(entity); + registry.emplace(entity); + + ASSERT_TRUE(observer.empty()); + + registry.emplace_or_replace(entity); + + ASSERT_EQ(observer.size(), 1u); + ASSERT_FALSE(observer.empty()); + ASSERT_EQ(*observer.data(), entity); + + observer.clear(); + registry.emplace(entity); + + ASSERT_TRUE(observer.empty()); + + registry.emplace_or_replace(entity); + registry.emplace(entity); + + ASSERT_TRUE(observer.empty()); + + registry.emplace_or_replace(entity); + + ASSERT_EQ(observer.size(), 1u); + ASSERT_FALSE(observer.empty()); + ASSERT_EQ(*observer.data(), entity); + + registry.erase(entity); + + ASSERT_TRUE(observer.empty()); + + registry.emplace_or_replace(entity); + + ASSERT_EQ(observer.size(), 1u); + ASSERT_FALSE(observer.empty()); + ASSERT_EQ(*observer.data(), entity); + + observer.clear(); + observer.disconnect(); + + registry.emplace_or_replace(entity); + + ASSERT_TRUE(observer.empty()); +} + +TEST(Observer, GroupCornerCase) { + constexpr auto add_collector = entt::collector.group(entt::exclude); + constexpr auto remove_collector = entt::collector.group(); + + entt::registry registry; + entt::observer add_observer{registry, add_collector}; + entt::observer remove_observer{registry, remove_collector}; + + const auto entity = registry.create(); + registry.emplace(entity); + + ASSERT_FALSE(add_observer.empty()); + ASSERT_TRUE(remove_observer.empty()); + + add_observer.clear(); + registry.emplace(entity); + + ASSERT_TRUE(add_observer.empty()); + ASSERT_FALSE(remove_observer.empty()); + + remove_observer.clear(); + registry.erase(entity); + + ASSERT_FALSE(add_observer.empty()); + ASSERT_TRUE(remove_observer.empty()); +} diff --git a/modules/entt/test/entt/entity/organizer.cpp b/modules/entt/test/entt/entity/organizer.cpp new file mode 100644 index 0000000..b3351b3 --- /dev/null +++ b/modules/entt/test/entt/entity/organizer.cpp @@ -0,0 +1,432 @@ +#include +#include +#include +#include + +void ro_int_rw_char_double(entt::view>, double &) {} +void ro_char_rw_int(entt::view>) {} +void ro_char_rw_double(entt::view>, double &) {} +void ro_int_double(entt::view>, const double &) {} +void sync_point(entt::registry &, entt::view>) {} + +struct clazz { + void ro_int_char_double(entt::view>, const double &) {} + void rw_int(entt::view>) {} + void rw_int_char(entt::view>) {} + void rw_int_char_double(entt::view>, double &) {} + + static void ro_int_with_payload(const clazz &, entt::view>) {} + static void ro_char_with_payload(const clazz &, entt::view>) {} + static void ro_int_char_with_payload(clazz &, entt::view>) {} +}; + +void to_args_integrity(entt::view> view, std::size_t &value, entt::registry ®istry) { + value = view.size(); +} + +TEST(Organizer, EmplaceFreeFunction) { + entt::organizer organizer; + entt::registry registry; + + organizer.emplace<&ro_int_rw_char_double>("t1"); + organizer.emplace<&ro_char_rw_int>("t2"); + organizer.emplace<&ro_char_rw_double>("t3"); + organizer.emplace<&ro_int_double>("t4"); + + const auto graph = organizer.graph(); + + ASSERT_EQ(graph.size(), 4u); + + ASSERT_STREQ(graph[0u].name(), "t1"); + ASSERT_STREQ(graph[1u].name(), "t2"); + ASSERT_STREQ(graph[2u].name(), "t3"); + ASSERT_STREQ(graph[3u].name(), "t4"); + + ASSERT_EQ(graph[0u].ro_count(), 1u); + ASSERT_EQ(graph[1u].ro_count(), 1u); + ASSERT_EQ(graph[2u].ro_count(), 1u); + ASSERT_EQ(graph[3u].ro_count(), 2u); + + ASSERT_EQ(graph[0u].rw_count(), 2u); + ASSERT_EQ(graph[1u].rw_count(), 1u); + ASSERT_EQ(graph[2u].rw_count(), 1u); + ASSERT_EQ(graph[3u].rw_count(), 0u); + + ASSERT_NE(graph[0u].info(), graph[1u].info()); + ASSERT_NE(graph[1u].info(), graph[2u].info()); + ASSERT_NE(graph[2u].info(), graph[3u].info()); + + ASSERT_TRUE(graph[0u].top_level()); + ASSERT_FALSE(graph[1u].top_level()); + ASSERT_FALSE(graph[2u].top_level()); + ASSERT_FALSE(graph[3u].top_level()); + + ASSERT_EQ(graph[0u].children().size(), 2u); + ASSERT_EQ(graph[1u].children().size(), 1u); + ASSERT_EQ(graph[2u].children().size(), 1u); + ASSERT_EQ(graph[3u].children().size(), 0u); + + ASSERT_EQ(graph[0u].children()[0u], 1u); + ASSERT_EQ(graph[0u].children()[1u], 2u); + ASSERT_EQ(graph[1u].children()[0u], 3u); + ASSERT_EQ(graph[2u].children()[0u], 3u); + + for(auto &&vertex: graph) { + ASSERT_NO_FATAL_FAILURE(vertex.callback()(vertex.data(), registry)); + } + + organizer.clear(); + + ASSERT_EQ(organizer.graph().size(), 0u); +} + +TEST(Organizer, EmplaceMemberFunction) { + entt::organizer organizer; + entt::registry registry; + clazz instance; + + organizer.emplace<&clazz::ro_int_char_double>(instance, "t1"); + organizer.emplace<&clazz::rw_int>(instance, "t2"); + organizer.emplace<&clazz::rw_int_char>(instance, "t3"); + organizer.emplace<&clazz::rw_int_char_double>(instance, "t4"); + + const auto graph = organizer.graph(); + + ASSERT_EQ(graph.size(), 4u); + + ASSERT_STREQ(graph[0u].name(), "t1"); + ASSERT_STREQ(graph[1u].name(), "t2"); + ASSERT_STREQ(graph[2u].name(), "t3"); + ASSERT_STREQ(graph[3u].name(), "t4"); + + ASSERT_EQ(graph[0u].ro_count(), 3u); + ASSERT_EQ(graph[1u].ro_count(), 0u); + ASSERT_EQ(graph[2u].ro_count(), 0u); + ASSERT_EQ(graph[3u].ro_count(), 0u); + + ASSERT_EQ(graph[0u].rw_count(), 0u); + ASSERT_EQ(graph[1u].rw_count(), 1u); + ASSERT_EQ(graph[2u].rw_count(), 2u); + ASSERT_EQ(graph[3u].rw_count(), 3u); + + ASSERT_NE(graph[0u].info(), graph[1u].info()); + ASSERT_NE(graph[1u].info(), graph[2u].info()); + ASSERT_NE(graph[2u].info(), graph[3u].info()); + + ASSERT_TRUE(graph[0u].top_level()); + ASSERT_FALSE(graph[1u].top_level()); + ASSERT_FALSE(graph[2u].top_level()); + ASSERT_FALSE(graph[3u].top_level()); + + ASSERT_EQ(graph[0u].children().size(), 1u); + ASSERT_EQ(graph[1u].children().size(), 1u); + ASSERT_EQ(graph[2u].children().size(), 1u); + ASSERT_EQ(graph[3u].children().size(), 0u); + + ASSERT_EQ(graph[0u].children()[0u], 1u); + ASSERT_EQ(graph[1u].children()[0u], 2u); + ASSERT_EQ(graph[2u].children()[0u], 3u); + + for(auto &&vertex: graph) { + ASSERT_NO_FATAL_FAILURE(vertex.callback()(vertex.data(), registry)); + } + + organizer.clear(); + + ASSERT_EQ(organizer.graph().size(), 0u); +} + +TEST(Organizer, EmplaceFreeFunctionWithPayload) { + entt::organizer organizer; + entt::registry registry; + clazz instance; + + organizer.emplace<&clazz::ro_int_char_double>(instance, "t1"); + organizer.emplace<&clazz::ro_int_with_payload>(instance, "t2"); + organizer.emplace<&clazz::ro_char_with_payload, const clazz>(instance, "t3"); + organizer.emplace<&clazz::ro_int_char_with_payload, clazz>(instance, "t4"); + organizer.emplace<&clazz::rw_int_char>(instance, "t5"); + + const auto graph = organizer.graph(); + + ASSERT_EQ(graph.size(), 5u); + + ASSERT_STREQ(graph[0u].name(), "t1"); + ASSERT_STREQ(graph[1u].name(), "t2"); + ASSERT_STREQ(graph[2u].name(), "t3"); + ASSERT_STREQ(graph[3u].name(), "t4"); + ASSERT_STREQ(graph[4u].name(), "t5"); + + ASSERT_EQ(graph[0u].ro_count(), 3u); + ASSERT_EQ(graph[1u].ro_count(), 1u); + ASSERT_EQ(graph[2u].ro_count(), 2u); + ASSERT_EQ(graph[3u].ro_count(), 2u); + ASSERT_EQ(graph[4u].ro_count(), 0u); + + ASSERT_EQ(graph[0u].rw_count(), 0u); + ASSERT_EQ(graph[1u].rw_count(), 0u); + ASSERT_EQ(graph[2u].rw_count(), 0u); + ASSERT_EQ(graph[3u].rw_count(), 1u); + ASSERT_EQ(graph[4u].rw_count(), 2u); + + ASSERT_NE(graph[0u].info(), graph[1u].info()); + ASSERT_NE(graph[1u].info(), graph[2u].info()); + ASSERT_NE(graph[2u].info(), graph[3u].info()); + ASSERT_NE(graph[3u].info(), graph[4u].info()); + + ASSERT_TRUE(graph[0u].top_level()); + ASSERT_TRUE(graph[1u].top_level()); + ASSERT_TRUE(graph[2u].top_level()); + ASSERT_FALSE(graph[3u].top_level()); + ASSERT_FALSE(graph[4u].top_level()); + + ASSERT_EQ(graph[0u].children().size(), 1u); + ASSERT_EQ(graph[1u].children().size(), 1u); + ASSERT_EQ(graph[2u].children().size(), 1u); + ASSERT_EQ(graph[3u].children().size(), 1u); + ASSERT_EQ(graph[4u].children().size(), 0u); + + ASSERT_EQ(graph[0u].children()[0u], 4u); + ASSERT_EQ(graph[1u].children()[0u], 4u); + ASSERT_EQ(graph[2u].children()[0u], 3u); + ASSERT_EQ(graph[3u].children()[0u], 4u); + + for(auto &&vertex: graph) { + ASSERT_NO_FATAL_FAILURE(vertex.callback()(vertex.data(), registry)); + } + + organizer.clear(); + + ASSERT_EQ(organizer.graph().size(), 0u); +} + +TEST(Organizer, EmplaceDirectFunction) { + entt::organizer organizer; + entt::registry registry; + clazz instance; + + // no aggressive comdat + auto t1 = +[](const void *, entt::registry ®) { reg.clear(); }; + auto t2 = +[](const void *, entt::registry ®) { reg.clear(); }; + auto t3 = +[](const void *, entt::registry ®) { reg.clear(); }; + auto t4 = +[](const void *, entt::registry ®) { reg.clear(); }; + + organizer.emplace(t1, nullptr, "t1"); + organizer.emplace(t2, &instance, "t2"); + organizer.emplace(t3, nullptr, "t3"); + organizer.emplace(t4, &instance, "t4"); + + const auto graph = organizer.graph(); + + ASSERT_EQ(graph.size(), 4u); + + ASSERT_STREQ(graph[0u].name(), "t1"); + ASSERT_STREQ(graph[1u].name(), "t2"); + ASSERT_STREQ(graph[2u].name(), "t3"); + ASSERT_STREQ(graph[3u].name(), "t4"); + + ASSERT_EQ(graph[0u].ro_count(), 0u); + ASSERT_EQ(graph[1u].ro_count(), 1u); + ASSERT_EQ(graph[2u].ro_count(), 1u); + ASSERT_EQ(graph[3u].ro_count(), 0u); + + ASSERT_EQ(graph[0u].rw_count(), 1u); + ASSERT_EQ(graph[1u].rw_count(), 0u); + ASSERT_EQ(graph[2u].rw_count(), 1u); + ASSERT_EQ(graph[3u].rw_count(), 3u); + + ASSERT_TRUE(graph[0u].callback() == t1); + ASSERT_TRUE(graph[1u].callback() == t2); + ASSERT_TRUE(graph[2u].callback() == t3); + ASSERT_TRUE(graph[3u].callback() == t4); + + ASSERT_EQ(graph[0u].data(), nullptr); + ASSERT_EQ(graph[1u].data(), &instance); + ASSERT_EQ(graph[2u].data(), nullptr); + ASSERT_EQ(graph[3u].data(), &instance); + + ASSERT_EQ(graph[0u].info(), entt::type_id()); + ASSERT_EQ(graph[1u].info(), entt::type_id()); + ASSERT_EQ(graph[2u].info(), entt::type_id()); + ASSERT_EQ(graph[3u].info(), entt::type_id()); + + ASSERT_TRUE(graph[0u].top_level()); + ASSERT_FALSE(graph[1u].top_level()); + ASSERT_FALSE(graph[2u].top_level()); + ASSERT_FALSE(graph[3u].top_level()); + + ASSERT_EQ(graph[0u].children().size(), 1u); + ASSERT_EQ(graph[1u].children().size(), 1u); + ASSERT_EQ(graph[2u].children().size(), 1u); + ASSERT_EQ(graph[3u].children().size(), 0u); + + ASSERT_EQ(graph[0u].children()[0u], 1u); + ASSERT_EQ(graph[1u].children()[0u], 2u); + ASSERT_EQ(graph[2u].children()[0u], 3u); + + for(auto &&vertex: graph) { + ASSERT_NO_FATAL_FAILURE(vertex.callback()(vertex.data(), registry)); + } + + organizer.clear(); + + ASSERT_EQ(organizer.graph().size(), 0u); +} + +TEST(Organizer, SyncPoint) { + entt::organizer organizer; + entt::registry registry; + clazz instance; + + organizer.emplace<&ro_int_double>("before"); + organizer.emplace<&sync_point>("sync_1"); + organizer.emplace<&clazz::ro_int_char_double>(instance, "mid_1"); + organizer.emplace<&ro_int_double>("mid_2"); + organizer.emplace<&sync_point>("sync_2"); + organizer.emplace<&ro_int_double>("after"); + + const auto graph = organizer.graph(); + + ASSERT_EQ(graph.size(), 6u); + + ASSERT_STREQ(graph[0u].name(), "before"); + ASSERT_STREQ(graph[1u].name(), "sync_1"); + ASSERT_STREQ(graph[2u].name(), "mid_1"); + ASSERT_STREQ(graph[3u].name(), "mid_2"); + ASSERT_STREQ(graph[4u].name(), "sync_2"); + ASSERT_STREQ(graph[5u].name(), "after"); + + ASSERT_TRUE(graph[0u].top_level()); + ASSERT_FALSE(graph[1u].top_level()); + ASSERT_FALSE(graph[2u].top_level()); + ASSERT_FALSE(graph[3u].top_level()); + ASSERT_FALSE(graph[4u].top_level()); + ASSERT_FALSE(graph[5u].top_level()); + + ASSERT_EQ(graph[0u].children().size(), 1u); + ASSERT_EQ(graph[1u].children().size(), 2u); + ASSERT_EQ(graph[2u].children().size(), 1u); + ASSERT_EQ(graph[3u].children().size(), 1u); + ASSERT_EQ(graph[4u].children().size(), 1u); + ASSERT_EQ(graph[5u].children().size(), 0u); + + ASSERT_EQ(graph[0u].children()[0u], 1u); + ASSERT_EQ(graph[1u].children()[0u], 2u); + ASSERT_EQ(graph[1u].children()[1u], 3u); + ASSERT_EQ(graph[2u].children()[0u], 4u); + ASSERT_EQ(graph[3u].children()[0u], 4u); + ASSERT_EQ(graph[4u].children()[0u], 5u); + + for(auto &&vertex: graph) { + ASSERT_NO_FATAL_FAILURE(vertex.callback()(vertex.data(), registry)); + } +} + +TEST(Organizer, Override) { + entt::organizer organizer; + + organizer.emplace<&ro_int_rw_char_double, const char, const double>("t1"); + organizer.emplace<&ro_char_rw_double, const double>("t2"); + organizer.emplace<&ro_int_double, double>("t3"); + + const auto graph = organizer.graph(); + + ASSERT_EQ(graph.size(), 3u); + + ASSERT_STREQ(graph[0u].name(), "t1"); + ASSERT_STREQ(graph[1u].name(), "t2"); + ASSERT_STREQ(graph[2u].name(), "t3"); + + ASSERT_TRUE(graph[0u].top_level()); + ASSERT_TRUE(graph[1u].top_level()); + ASSERT_FALSE(graph[2u].top_level()); + + ASSERT_EQ(graph[0u].children().size(), 1u); + ASSERT_EQ(graph[1u].children().size(), 1u); + ASSERT_EQ(graph[2u].children().size(), 0u); + + ASSERT_EQ(graph[0u].children()[0u], 2u); + ASSERT_EQ(graph[1u].children()[0u], 2u); +} + +TEST(Organizer, Prepare) { + entt::organizer organizer; + entt::registry registry; + clazz instance; + + organizer.emplace<&ro_int_double>(); + organizer.emplace<&clazz::rw_int_char>(instance); + + const auto graph = organizer.graph(); + + ASSERT_FALSE(registry.ctx().contains()); + ASSERT_FALSE(registry.ctx().contains()); + ASSERT_FALSE(registry.ctx().contains()); + + for(auto &&vertex: graph) { + vertex.prepare(registry); + } + + ASSERT_FALSE(registry.ctx().contains()); + ASSERT_FALSE(registry.ctx().contains()); + ASSERT_TRUE(registry.ctx().contains()); +} + +TEST(Organizer, Dependencies) { + entt::organizer organizer; + clazz instance; + + organizer.emplace<&ro_int_double>(); + organizer.emplace<&clazz::rw_int_char>(instance); + organizer.emplace(+[](const void *, entt::registry &) {}); + + const auto graph = organizer.graph(); + const entt::type_info *buffer[5u]{}; + + ASSERT_EQ(graph.size(), 3u); + + ASSERT_EQ(graph[0u].ro_count(), 2u); + ASSERT_EQ(graph[0u].rw_count(), 0u); + + ASSERT_EQ(graph[0u].ro_dependency(buffer, 0u), 0u); + ASSERT_EQ(graph[0u].rw_dependency(buffer, 2u), 0u); + + ASSERT_EQ(graph[0u].ro_dependency(buffer, 5u), 2u); + ASSERT_EQ(*buffer[0u], entt::type_id()); + ASSERT_EQ(*buffer[1u], entt::type_id()); + + ASSERT_EQ(graph[1u].ro_count(), 0u); + ASSERT_EQ(graph[1u].rw_count(), 2u); + + ASSERT_EQ(graph[1u].ro_dependency(buffer, 2u), 0u); + ASSERT_EQ(graph[1u].rw_dependency(buffer, 0u), 0u); + + ASSERT_EQ(graph[1u].rw_dependency(buffer, 5u), 2u); + ASSERT_EQ(*buffer[0u], entt::type_id()); + ASSERT_EQ(*buffer[1u], entt::type_id()); + + ASSERT_EQ(graph[2u].ro_count(), 1u); + ASSERT_EQ(graph[2u].rw_count(), 1u); + + ASSERT_EQ(graph[2u].ro_dependency(buffer, 2u), 1u); + ASSERT_EQ(graph[2u].rw_dependency(buffer, 0u), 0u); + + ASSERT_EQ(graph[2u].ro_dependency(buffer, 5u), 1u); + ASSERT_EQ(*buffer[0u], entt::type_id()); + + ASSERT_EQ(graph[2u].rw_dependency(buffer, 5u), 1u); + ASSERT_EQ(*buffer[0u], entt::type_id()); +} + +TEST(Organizer, ToArgsIntegrity) { + entt::organizer organizer; + entt::registry registry; + + organizer.emplace<&to_args_integrity>(); + registry.ctx().emplace(42u); + + auto graph = organizer.graph(); + graph[0u].callback()(graph[0u].data(), registry); + + ASSERT_EQ(registry.ctx().at(), 0u); +} diff --git a/modules/entt/test/entt/entity/prototype.cpp b/modules/entt/test/entt/entity/prototype.cpp deleted file mode 100644 index 22a467e..0000000 --- a/modules/entt/test/entt/entity/prototype.cpp +++ /dev/null @@ -1,161 +0,0 @@ -#include -#include -#include - -TEST(Prototype, SameRegistry) { - entt::registry registry; - entt::prototype prototype{registry}; - - ASSERT_EQ(®istry, &prototype.backend()); - ASSERT_EQ(®istry, &std::as_const(prototype).backend()); - ASSERT_FALSE(registry.empty()); - ASSERT_FALSE((prototype.has())); - - ASSERT_EQ(prototype.set(2), 2); - ASSERT_EQ(prototype.set(3), 3); - ASSERT_EQ(prototype.set('c'), 'c'); - - ASSERT_EQ(prototype.get(), 3); - ASSERT_EQ(std::as_const(prototype).get(), 'c'); - ASSERT_EQ(std::get<0>(prototype.get()), 3); - ASSERT_EQ(std::get<1>(std::as_const(prototype).get()), 'c'); - - ASSERT_NE(prototype.try_get(), nullptr); - ASSERT_NE(prototype.try_get(), nullptr); - ASSERT_EQ(prototype.try_get(), nullptr); - ASSERT_EQ(*prototype.try_get(), 3); - ASSERT_EQ(*std::as_const(prototype).try_get(), 'c'); - ASSERT_EQ(*std::get<0>(prototype.try_get()), 3); - ASSERT_EQ(*std::get<1>(std::as_const(prototype).try_get()), 'c'); - - const auto e0 = prototype.create(); - - ASSERT_TRUE((prototype.has())); - ASSERT_FALSE(registry.orphan(e0)); - - const auto e1 = prototype(); - prototype(e0); - - ASSERT_FALSE(registry.orphan(e0)); - ASSERT_FALSE(registry.orphan(e1)); - - ASSERT_TRUE((registry.has(e0))); - ASSERT_TRUE((registry.has(e1))); - - registry.remove(e0); - registry.remove(e1); - prototype.unset(); - - ASSERT_FALSE((prototype.has())); - ASSERT_FALSE((prototype.has())); - ASSERT_TRUE((prototype.has())); - - prototype(e0); - prototype(e1); - - ASSERT_FALSE(registry.has(e0)); - ASSERT_FALSE(registry.has(e1)); - - ASSERT_EQ(registry.get(e0), 'c'); - ASSERT_EQ(registry.get(e1), 'c'); - - registry.get(e0) = '*'; - prototype.assign(e0); - - ASSERT_EQ(registry.get(e0), '*'); - - registry.get(e1) = '*'; - prototype.assign_or_replace(e1); - - ASSERT_EQ(registry.get(e1), 'c'); -} - -TEST(Prototype, OtherRegistry) { - entt::registry registry; - entt::registry repository; - entt::prototype prototype{repository}; - - ASSERT_TRUE(registry.empty()); - ASSERT_FALSE((prototype.has())); - - ASSERT_EQ(prototype.set(2), 2); - ASSERT_EQ(prototype.set(3), 3); - ASSERT_EQ(prototype.set('c'), 'c'); - - ASSERT_EQ(prototype.get(), 3); - ASSERT_EQ(std::as_const(prototype).get(), 'c'); - ASSERT_EQ(std::get<0>(prototype.get()), 3); - ASSERT_EQ(std::get<1>(std::as_const(prototype).get()), 'c'); - - const auto e0 = prototype.create(registry); - - ASSERT_TRUE((prototype.has())); - ASSERT_FALSE(registry.orphan(e0)); - - const auto e1 = prototype(registry); - prototype(registry, e0); - - ASSERT_FALSE(registry.orphan(e0)); - ASSERT_FALSE(registry.orphan(e1)); - - ASSERT_TRUE((registry.has(e0))); - ASSERT_TRUE((registry.has(e1))); - - registry.remove(e0); - registry.remove(e1); - prototype.unset(); - - ASSERT_FALSE((prototype.has())); - ASSERT_FALSE((prototype.has())); - ASSERT_TRUE((prototype.has())); - - prototype(registry, e0); - prototype(registry, e1); - - ASSERT_FALSE(registry.has(e0)); - ASSERT_FALSE(registry.has(e1)); - - ASSERT_EQ(registry.get(e0), 'c'); - ASSERT_EQ(registry.get(e1), 'c'); - - registry.get(e0) = '*'; - prototype.assign(registry, e0); - - ASSERT_EQ(registry.get(e0), '*'); - - registry.get(e1) = '*'; - prototype.assign_or_replace(registry, e1); - - ASSERT_EQ(registry.get(e1), 'c'); -} - -TEST(Prototype, RAII) { - entt::registry registry; - - { - entt::prototype prototype{registry}; - prototype.set(0); - - ASSERT_FALSE(registry.empty()); - } - - ASSERT_TRUE(registry.empty()); -} - -TEST(Prototype, MoveConstructionAssignment) { - entt::registry registry; - - entt::prototype prototype{registry}; - prototype.set(0); - auto other{std::move(prototype)}; - const auto e0 = other(); - - ASSERT_EQ(registry.size(), entt::registry::size_type{2}); - ASSERT_TRUE(registry.has(e0)); - - prototype = std::move(other); - const auto e1 = prototype(); - - ASSERT_EQ(registry.size(), entt::registry::size_type{3}); - ASSERT_TRUE(registry.has(e1)); -} diff --git a/modules/entt/test/entt/entity/registry.cpp b/modules/entt/test/entt/entity/registry.cpp index 8e78c88..f5639de 100644 --- a/modules/entt/test/entt/entity/registry.cpp +++ b/modules/entt/test/entt/entity/registry.cpp @@ -1,130 +1,227 @@ -#include -#include +#include #include #include #include -#include +#include #include +#include +#include #include -#include +#include +#include +#include #include - -ENTT_NAMED_TYPE(int) +#include struct empty_type {}; +struct no_eto_type { + static constexpr std::size_t page_size = ENTT_PACKED_PAGE; +}; + +bool operator==(const no_eto_type &lhs, const no_eto_type &rhs) { + return &lhs == &rhs; +} + +struct stable_type { + static constexpr auto in_place_delete = true; + int value; +}; + +struct non_default_constructible { + non_default_constructible(int v) + : value{v} {} + + int value; +}; + +struct aggregate { + int value{}; +}; + struct listener { template - void incr(entt::registry ®istry, entt::entity entity, const Component &) { - ASSERT_TRUE(registry.valid(entity)); - ASSERT_TRUE(registry.has(entity)); + static void sort(entt::registry ®istry) { + registry.sort([](auto lhs, auto rhs) { return lhs < rhs; }); + } + + template + void incr(const entt::registry &, entt::entity entity) { last = entity; ++counter; } template - void decr(entt::registry ®istry, entt::entity entity) { - ASSERT_TRUE(registry.valid(entity)); - ASSERT_TRUE(registry.has(entity)); + void decr(const entt::registry &, entt::entity entity) { last = entity; --counter; } - entt::entity last; + entt::entity last{entt::null}; int counter{0}; }; +struct owner { + void receive(const entt::registry &ref) { + parent = &ref; + } + + const entt::registry *parent{nullptr}; +}; + TEST(Registry, Context) { entt::registry registry; + auto &ctx = registry.ctx(); + const auto &cctx = std::as_const(registry).ctx(); - ASSERT_EQ(registry.try_ctx(), nullptr); - ASSERT_EQ(registry.try_ctx(), nullptr); - ASSERT_EQ(registry.try_ctx(), nullptr); + ASSERT_FALSE(ctx.contains()); + ASSERT_FALSE(cctx.contains()); + ASSERT_EQ(ctx.find(), nullptr); + ASSERT_EQ(cctx.find(), nullptr); - registry.set(); - registry.set(); - registry.ctx_or_set(); + ctx.emplace(); + ctx.emplace(); - ASSERT_NE(registry.try_ctx(), nullptr); - ASSERT_NE(registry.try_ctx(), nullptr); - ASSERT_NE(registry.try_ctx(), nullptr); + ASSERT_TRUE(ctx.contains()); + ASSERT_TRUE(cctx.contains()); + ASSERT_NE(ctx.find(), nullptr); + ASSERT_NE(cctx.find(), nullptr); - registry.unset(); - registry.unset(); + ASSERT_FALSE(ctx.erase()); + ASSERT_TRUE(ctx.erase()); - ASSERT_NE(registry.try_ctx(), nullptr); - ASSERT_EQ(registry.try_ctx(), nullptr); - ASSERT_EQ(registry.try_ctx(), nullptr); + ASSERT_TRUE(ctx.contains()); + ASSERT_FALSE(cctx.contains()); + ASSERT_NE(ctx.find(), nullptr); + ASSERT_EQ(cctx.find(), nullptr); - registry.set('c'); - registry.set(0); - registry.set(1.); - registry.set(42); + ASSERT_FALSE(ctx.erase()); + ASSERT_TRUE(ctx.erase()); - ASSERT_EQ(registry.ctx_or_set('a'), 'c'); - ASSERT_NE(registry.try_ctx(), nullptr); - ASSERT_EQ(registry.try_ctx(), ®istry.ctx()); - ASSERT_EQ(registry.ctx(), std::as_const(registry).ctx()); + ctx.emplace('c'); + ctx.emplace(42); - ASSERT_EQ(registry.ctx(), 42); - ASSERT_NE(registry.try_ctx(), nullptr); - ASSERT_EQ(registry.try_ctx(), ®istry.ctx()); - ASSERT_EQ(registry.ctx(), std::as_const(registry).ctx()); + ASSERT_EQ(ctx.emplace('a'), 'c'); + ASSERT_EQ(ctx.find(), cctx.find()); + ASSERT_EQ(ctx.at(), cctx.at()); + ASSERT_EQ(ctx.at(), 'c'); - ASSERT_EQ(registry.ctx(), 1.); - ASSERT_NE(registry.try_ctx(), nullptr); - ASSERT_EQ(registry.try_ctx(), ®istry.ctx()); - ASSERT_EQ(registry.ctx(), std::as_const(registry).ctx()); + ASSERT_EQ(ctx.emplace(0), 42); + ASSERT_EQ(ctx.find(), cctx.find()); + ASSERT_EQ(ctx.at(), cctx.at()); + ASSERT_EQ(ctx.at(), 42); - ASSERT_EQ(registry.try_ctx(), nullptr); + ASSERT_EQ(ctx.find(), nullptr); + ASSERT_EQ(cctx.find(), nullptr); } -TEST(Registry, Types) { +TEST(Registry, ContextHint) { + using namespace entt::literals; + entt::registry registry; - ASSERT_EQ(registry.type(), registry.type()); - ASSERT_NE(registry.type(), registry.type()); + auto &ctx = registry.ctx(); + const auto &cctx = std::as_const(registry).ctx(); + + ctx.emplace(42); + ctx.emplace_hint("other"_hs, 3); + + ASSERT_TRUE(ctx.contains()); + ASSERT_TRUE(cctx.contains("other"_hs)); + ASSERT_FALSE(ctx.contains("other"_hs)); + + ASSERT_NE(cctx.find(), nullptr); + ASSERT_NE(ctx.find("other"_hs), nullptr); + ASSERT_EQ(cctx.find("other"_hs), nullptr); + + ASSERT_EQ(ctx.at(), 42); + ASSERT_EQ(cctx.at("other"_hs), 3); + + ASSERT_FALSE(ctx.erase("other"_hs)); + ASSERT_TRUE(ctx.erase()); + + ASSERT_TRUE(cctx.contains("other"_hs)); + ASSERT_EQ(ctx.at("other"_hs), 3); + + ASSERT_TRUE(ctx.erase("other"_hs)); + + ASSERT_FALSE(cctx.contains("other"_hs)); + ASSERT_EQ(ctx.find("other"_hs), nullptr); +} + +TEST(Registry, ContextAsRef) { + entt::registry registry; + int value{3}; + + registry.ctx().emplace(value); + + ASSERT_NE(registry.ctx().find(), nullptr); + ASSERT_NE(registry.ctx().find(), nullptr); + ASSERT_NE(std::as_const(registry).ctx().find(), nullptr); + ASSERT_EQ(registry.ctx().at(), 3); + ASSERT_EQ(registry.ctx().at(), 3); + + registry.ctx().at() = 42; + + ASSERT_EQ(registry.ctx().at(), 42); + ASSERT_EQ(value, 42); + + value = 3; + + ASSERT_EQ(std::as_const(registry).ctx().at(), 3); +} + +TEST(Registry, ContextAsConstRef) { + entt::registry registry; + int value{3}; + + registry.ctx().emplace(value); + + ASSERT_EQ(registry.ctx().find(), nullptr); + ASSERT_NE(registry.ctx().find(), nullptr); + ASSERT_NE(std::as_const(registry).ctx().find(), nullptr); + ASSERT_EQ(registry.ctx().at(), 3); + + value = 42; + + ASSERT_EQ(std::as_const(registry).ctx().at(), 42); } TEST(Registry, Functionalities) { + using traits_type = entt::entt_traits; + entt::registry registry; - ASSERT_EQ(registry.size(), entt::registry::size_type{0}); - ASSERT_EQ(registry.alive(), entt::registry::size_type{0}); - ASSERT_NO_THROW(registry.reserve(42)); - ASSERT_NO_THROW(registry.reserve(8)); - ASSERT_NO_THROW(registry.reserve(8)); + ASSERT_EQ(registry.size(), 0u); + ASSERT_EQ(registry.alive(), 0u); + ASSERT_NO_FATAL_FAILURE(registry.reserve(42)); + ASSERT_EQ(registry.capacity(), 42u); ASSERT_TRUE(registry.empty()); - ASSERT_EQ(registry.capacity(), entt::registry::size_type{42}); - ASSERT_EQ(registry.capacity(), entt::registry::size_type{8}); - ASSERT_EQ(registry.capacity(), entt::registry::size_type{8}); - ASSERT_EQ(registry.size(), entt::registry::size_type{0}); - ASSERT_EQ(registry.size(), entt::registry::size_type{0}); - ASSERT_TRUE(registry.empty()); - ASSERT_TRUE(registry.empty()); + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_TRUE(registry.storage().empty()); + ASSERT_TRUE(registry.storage().empty()); const auto e0 = registry.create(); const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(e1); + registry.emplace(e1); - ASSERT_TRUE(registry.has<>(e0)); - ASSERT_TRUE(registry.has<>(e1)); + ASSERT_TRUE(registry.all_of<>(e0)); + ASSERT_FALSE(registry.any_of<>(e1)); - ASSERT_EQ(registry.size(), entt::registry::size_type{1}); - ASSERT_EQ(registry.size(), entt::registry::size_type{1}); - ASSERT_FALSE(registry.empty()); - ASSERT_FALSE(registry.empty()); + ASSERT_EQ(registry.storage().size(), 1u); + ASSERT_EQ(registry.storage().size(), 1u); + ASSERT_FALSE(registry.storage().empty()); + ASSERT_FALSE(registry.storage().empty()); ASSERT_NE(e0, e1); - ASSERT_FALSE(registry.has(e0)); - ASSERT_TRUE(registry.has(e1)); - ASSERT_FALSE(registry.has(e0)); - ASSERT_TRUE(registry.has(e1)); - ASSERT_FALSE((registry.has(e0))); - ASSERT_TRUE((registry.has(e1))); + ASSERT_FALSE((registry.all_of(e0))); + ASSERT_TRUE((registry.all_of(e1))); + ASSERT_FALSE((registry.any_of(e0))); + ASSERT_TRUE((registry.any_of(e1))); ASSERT_EQ(registry.try_get(e0), nullptr); ASSERT_NE(registry.try_get(e1), nullptr); @@ -133,25 +230,22 @@ TEST(Registry, Functionalities) { ASSERT_EQ(registry.try_get(e0), nullptr); ASSERT_EQ(registry.try_get(e1), nullptr); - ASSERT_EQ(registry.assign(e0, 42), 42); - ASSERT_EQ(registry.assign(e0, 'c'), 'c'); - ASSERT_NO_THROW(registry.remove(e1)); - ASSERT_NO_THROW(registry.remove(e1)); + ASSERT_EQ(registry.emplace(e0, 42), 42); + ASSERT_EQ(registry.emplace(e0, 'c'), 'c'); + ASSERT_NO_FATAL_FAILURE(registry.erase(e1)); + ASSERT_NO_FATAL_FAILURE(registry.erase(e1)); - ASSERT_TRUE(registry.has(e0)); - ASSERT_FALSE(registry.has(e1)); - ASSERT_TRUE(registry.has(e0)); - ASSERT_FALSE(registry.has(e1)); - ASSERT_TRUE((registry.has(e0))); - ASSERT_FALSE((registry.has(e1))); + ASSERT_TRUE((registry.all_of(e0))); + ASSERT_FALSE((registry.all_of(e1))); + ASSERT_TRUE((registry.any_of(e0))); + ASSERT_FALSE((registry.any_of(e1))); const auto e2 = registry.create(); - registry.assign_or_replace(e2, registry.get(e0)); - registry.assign_or_replace(e2, registry.get(e0)); + registry.emplace_or_replace(e2, registry.get(e0)); + registry.emplace_or_replace(e2, registry.get(e0)); - ASSERT_TRUE(registry.has(e2)); - ASSERT_TRUE(registry.has(e2)); + ASSERT_TRUE((registry.all_of(e2))); ASSERT_EQ(registry.get(e0), 42); ASSERT_EQ(registry.get(e0), 'c'); @@ -171,152 +265,522 @@ TEST(Registry, Functionalities) { ASSERT_NE(®istry.get(e0), ®istry.get(e2)); ASSERT_NE(®istry.get(e0), ®istry.get(e2)); - ASSERT_NO_THROW(registry.replace(e0, 0)); - ASSERT_EQ(registry.get(e0), 0); + ASSERT_EQ(registry.patch(e0, [](auto &instance) { instance = 2; }), 2); + ASSERT_EQ(registry.replace(e0, 3), 3); - ASSERT_NO_THROW(registry.assign_or_replace(e0, 1)); - ASSERT_NO_THROW(registry.assign_or_replace(e1, 1)); + ASSERT_NO_FATAL_FAILURE(registry.emplace_or_replace(e0, 1)); + ASSERT_NO_FATAL_FAILURE(registry.emplace_or_replace(e1, 1)); ASSERT_EQ(static_cast(registry).get(e0), 1); ASSERT_EQ(static_cast(registry).get(e1), 1); - ASSERT_EQ(registry.size(), entt::registry::size_type{3}); - ASSERT_EQ(registry.alive(), entt::registry::size_type{3}); + ASSERT_EQ(registry.size(), 3u); + ASSERT_EQ(registry.alive(), 3u); ASSERT_FALSE(registry.empty()); - ASSERT_EQ(registry.version(e2), entt::registry::version_type{0}); - ASSERT_EQ(registry.current(e2), entt::registry::version_type{0}); - ASSERT_NO_THROW(registry.destroy(e2)); - ASSERT_EQ(registry.version(e2), entt::registry::version_type{0}); - ASSERT_EQ(registry.current(e2), entt::registry::version_type{1}); + ASSERT_EQ(traits_type::to_version(e2), 0u); + ASSERT_EQ(registry.current(e2), 0u); + ASSERT_NO_FATAL_FAILURE(registry.destroy(e2)); + ASSERT_EQ(traits_type::to_version(e2), 0u); + ASSERT_EQ(registry.current(e2), 1u); ASSERT_TRUE(registry.valid(e0)); ASSERT_TRUE(registry.valid(e1)); ASSERT_FALSE(registry.valid(e2)); - ASSERT_EQ(registry.size(), entt::registry::size_type{3}); - ASSERT_EQ(registry.alive(), entt::registry::size_type{2}); + ASSERT_EQ(registry.size(), 3u); + ASSERT_EQ(registry.alive(), 2u); ASSERT_FALSE(registry.empty()); - ASSERT_NO_THROW(registry.reset()); + ASSERT_NO_FATAL_FAILURE(registry.clear()); - ASSERT_EQ(registry.size(), entt::registry::size_type{3}); - ASSERT_EQ(registry.alive(), entt::registry::size_type{0}); + ASSERT_EQ(registry.size(), 3u); + ASSERT_EQ(registry.alive(), 0u); ASSERT_TRUE(registry.empty()); const auto e3 = registry.create(); - ASSERT_EQ(registry.get_or_assign(e3, 3), 3); - ASSERT_EQ(registry.get_or_assign(e3, 'c'), 'c'); + ASSERT_EQ(registry.get_or_emplace(e3, 3), 3); + ASSERT_EQ(registry.get_or_emplace(e3, 'c'), 'c'); - ASSERT_EQ(registry.size(), entt::registry::size_type{1}); - ASSERT_EQ(registry.size(), entt::registry::size_type{1}); - ASSERT_FALSE(registry.empty()); - ASSERT_FALSE(registry.empty()); - ASSERT_TRUE(registry.has(e3)); - ASSERT_TRUE(registry.has(e3)); + ASSERT_EQ(registry.storage().size(), 1u); + ASSERT_EQ(registry.storage().size(), 1u); + ASSERT_FALSE(registry.storage().empty()); + ASSERT_FALSE(registry.storage().empty()); + ASSERT_TRUE((registry.all_of(e3))); ASSERT_EQ(registry.get(e3), 3); ASSERT_EQ(registry.get(e3), 'c'); - ASSERT_NO_THROW(registry.reset()); + ASSERT_NO_FATAL_FAILURE(registry.clear()); - ASSERT_EQ(registry.size(), entt::registry::size_type{0}); - ASSERT_EQ(registry.size(), entt::registry::size_type{1}); - ASSERT_TRUE(registry.empty()); - ASSERT_FALSE(registry.empty()); + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 1u); + ASSERT_TRUE(registry.storage().empty()); + ASSERT_FALSE(registry.storage().empty()); - ASSERT_NO_THROW(registry.reset()); + ASSERT_NO_FATAL_FAILURE(registry.clear()); - ASSERT_EQ(registry.size(), entt::registry::size_type{0}); - ASSERT_EQ(registry.size(), entt::registry::size_type{0}); - ASSERT_TRUE(registry.empty()); - ASSERT_TRUE(registry.empty()); + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_TRUE(registry.storage().empty()); + ASSERT_TRUE(registry.storage().empty()); const auto e4 = registry.create(); const auto e5 = registry.create(); - registry.assign(e4); + registry.emplace(e4); + + ASSERT_EQ(registry.remove(e4), 1u); + ASSERT_EQ(registry.remove(e5), 0u); + + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_TRUE(registry.storage().empty()); +} + +TEST(Registry, Constructors) { + entt::registry registry; + entt::registry other{42}; + const entt::entity entity = entt::tombstone; + + ASSERT_TRUE(registry.empty()); + ASSERT_TRUE(other.empty()); + + ASSERT_EQ(registry.released(), entity); + ASSERT_EQ(other.released(), entity); +} + +TEST(Registry, Move) { + entt::registry registry; + const auto entity = registry.create(); + owner test{}; + + registry.on_construct().connect<&owner::receive>(test); + registry.on_destroy().connect<&owner::receive>(test); + + ASSERT_EQ(test.parent, nullptr); + + registry.emplace(entity); + + ASSERT_EQ(test.parent, ®istry); + + entt::registry other{std::move(registry)}; + other.erase(entity); + + registry = {}; + registry.emplace(registry.create(entity)); + + ASSERT_EQ(test.parent, &other); + + registry = std::move(other); + registry.emplace(entity); + registry.emplace(registry.create(entity)); + + ASSERT_EQ(test.parent, ®istry); +} - ASSERT_NO_THROW(registry.reset(e4)); - ASSERT_NO_THROW(registry.reset(e5)); +TEST(Registry, ReplaceAggregate) { + entt::registry registry; + const auto entity = registry.create(); - ASSERT_EQ(registry.size(), entt::registry::size_type{0}); - ASSERT_EQ(registry.size(), entt::registry::size_type{0}); - ASSERT_TRUE(registry.empty()); + registry.emplace(entity, 0); + auto &instance = registry.replace(entity, 42); - ASSERT_EQ(registry.capacity(), entt::registry::size_type{8}); - ASSERT_EQ(registry.capacity(), entt::registry::size_type{8}); + ASSERT_EQ(instance.value, 42); +} - registry.shrink_to_fit(); - registry.shrink_to_fit(); +TEST(Registry, EmplaceOrReplaceAggregate) { + entt::registry registry; + const auto entity = registry.create(); + auto &instance = registry.emplace_or_replace(entity, 42); - ASSERT_EQ(registry.capacity(), entt::registry::size_type{}); - ASSERT_EQ(registry.capacity(), entt::registry::size_type{}); + ASSERT_EQ(instance.value, 42); } TEST(Registry, Identifiers) { + using traits_type = entt::entt_traits; + entt::registry registry; const auto pre = registry.create(); - ASSERT_EQ(pre, registry.entity(pre)); + ASSERT_EQ(traits_type::to_integral(pre), traits_type::to_entity(pre)); - registry.destroy(pre); + registry.release(pre); const auto post = registry.create(); ASSERT_NE(pre, post); - ASSERT_EQ(entt::registry::entity(pre), entt::registry::entity(post)); - ASSERT_NE(entt::registry::version(pre), entt::registry::version(post)); - ASSERT_NE(registry.version(pre), registry.current(pre)); - ASSERT_EQ(registry.version(post), registry.current(post)); + ASSERT_EQ(traits_type::to_entity(pre), traits_type::to_entity(post)); + ASSERT_NE(traits_type::to_version(pre), traits_type::to_version(post)); + ASSERT_NE(traits_type::to_version(pre), registry.current(pre)); + ASSERT_EQ(traits_type::to_version(post), registry.current(post)); + + const auto invalid = traits_type::combine(traits_type::to_entity(post) + 1u, {}); + + ASSERT_EQ(traits_type::to_version(invalid), typename traits_type::version_type{}); + ASSERT_EQ(registry.current(invalid), traits_type::to_version(entt::tombstone)); +} + +TEST(Registry, Data) { + entt::registry registry; + + ASSERT_EQ(std::as_const(registry).data(), nullptr); + + const auto entity = registry.create(); + + ASSERT_EQ(*std::as_const(registry).data(), entity); + + const auto other = registry.create(); + registry.release(entity); + + ASSERT_NE(*std::as_const(registry).data(), entity); + ASSERT_EQ(*(std::as_const(registry).data() + 1u), other); } -TEST(Registry, RawData) { +TEST(Registry, CreateManyEntitiesAtOnce) { + using traits_type = entt::entt_traits; + entt::registry registry; + entt::entity entities[3]; + const auto entity = registry.create(); + registry.release(registry.create()); + registry.release(entity); + registry.release(registry.create()); + + registry.create(std::begin(entities), std::end(entities)); + + ASSERT_TRUE(registry.valid(entities[0])); + ASSERT_TRUE(registry.valid(entities[1])); + ASSERT_TRUE(registry.valid(entities[2])); - ASSERT_EQ(registry.raw(), nullptr); - ASSERT_EQ(std::as_const(registry).raw(), nullptr); - ASSERT_EQ(std::as_const(registry).data(), nullptr); + ASSERT_EQ(traits_type::to_entity(entities[0]), 0u); + ASSERT_EQ(traits_type::to_version(entities[0]), 2u); - registry.assign(entity, 42); + ASSERT_EQ(traits_type::to_entity(entities[1]), 1u); + ASSERT_EQ(traits_type::to_version(entities[1]), 1u); - ASSERT_NE(registry.raw(), nullptr); - ASSERT_NE(std::as_const(registry).raw(), nullptr); - ASSERT_NE(std::as_const(registry).data(), nullptr); + ASSERT_EQ(traits_type::to_entity(entities[2]), 2u); + ASSERT_EQ(traits_type::to_version(entities[2]), 0u); +} + +TEST(Registry, CreateManyEntitiesAtOnceWithListener) { + entt::registry registry; + entt::entity entities[3]; + listener listener; + + registry.on_construct().connect<&listener::incr>(listener); + registry.create(std::begin(entities), std::end(entities)); + registry.insert(std::begin(entities), std::end(entities), 42); + registry.insert(std::begin(entities), std::end(entities), 'c'); + + ASSERT_EQ(registry.get(entities[0]), 42); + ASSERT_EQ(registry.get(entities[1]), 'c'); + ASSERT_EQ(listener.counter, 3); + + registry.on_construct().disconnect<&listener::incr>(listener); + registry.on_construct().connect<&listener::incr>(listener); + registry.create(std::begin(entities), std::end(entities)); + registry.insert(std::begin(entities), std::end(entities), 'a'); + registry.insert(std::begin(entities), std::end(entities)); + + ASSERT_TRUE(registry.all_of(entities[0])); + ASSERT_EQ(registry.get(entities[2]), 'a'); + ASSERT_EQ(listener.counter, 6); +} + +TEST(Registry, CreateWithHint) { + using traits_type = entt::entt_traits; + + entt::registry registry; + auto e3 = registry.create(entt::entity{3}); + auto e2 = registry.create(entt::entity{3}); + + ASSERT_EQ(e2, entt::entity{2}); + ASSERT_FALSE(registry.valid(entt::entity{1})); + ASSERT_EQ(e3, entt::entity{3}); + + registry.release(e2); + + ASSERT_EQ(traits_type::to_version(e2), 0u); + ASSERT_EQ(registry.current(e2), 1u); + + e2 = registry.create(); + auto e1 = registry.create(entt::entity{2}); + + ASSERT_EQ(traits_type::to_entity(e2), 2u); + ASSERT_EQ(traits_type::to_version(e2), 1u); + + ASSERT_EQ(traits_type::to_entity(e1), 1u); + ASSERT_EQ(traits_type::to_version(e1), 0u); + + registry.release(e1); + registry.release(e2); + auto e0 = registry.create(entt::entity{0}); + + ASSERT_EQ(e0, entt::entity{0}); + ASSERT_EQ(traits_type::to_version(e0), 0u); +} + +TEST(Registry, CreateClearCycle) { + using traits_type = entt::entt_traits; + + entt::registry registry; + entt::entity pre{}, post{}; + + for(int i = 0; i < 10; ++i) { + const auto entity = registry.create(); + registry.emplace(entity); + } + + registry.clear(); + + for(int i = 0; i < 7; ++i) { + const auto entity = registry.create(); + registry.emplace(entity); + if(i == 3) { pre = entity; } + } + + registry.clear(); + + for(int i = 0; i < 5; ++i) { + const auto entity = registry.create(); + if(i == 3) { post = entity; } + } - ASSERT_EQ(*registry.raw(), 42); - ASSERT_EQ(*std::as_const(registry).raw(), 42); - ASSERT_EQ(*std::as_const(registry).data(), entity); + ASSERT_FALSE(registry.valid(pre)); + ASSERT_TRUE(registry.valid(post)); + ASSERT_NE(traits_type::to_version(pre), traits_type::to_version(post)); + ASSERT_EQ(traits_type::to_version(pre) + 1, traits_type::to_version(post)); + ASSERT_EQ(registry.current(pre), registry.current(post)); } -TEST(Registry, CreateDestroyCornerCase) { +TEST(Registry, CreateDestroyReleaseCornerCase) { entt::registry registry; const auto e0 = registry.create(); const auto e1 = registry.create(); registry.destroy(e0); - registry.destroy(e1); + registry.release(e1); registry.each([](auto) { FAIL(); }); - ASSERT_EQ(registry.current(e0), entt::registry::version_type{1}); - ASSERT_EQ(registry.current(e1), entt::registry::version_type{1}); + ASSERT_EQ(registry.current(e0), 1u); + ASSERT_EQ(registry.current(e1), 1u); } -TEST(Registry, VersionOverflow) { +TEST(Registry, DestroyVersion) { entt::registry registry; + const auto e0 = registry.create(); + const auto e1 = registry.create(); + + ASSERT_EQ(registry.current(e0), 0u); + ASSERT_EQ(registry.current(e1), 0u); + + registry.destroy(e0); + registry.destroy(e1, 3); + + ASSERT_EQ(registry.current(e0), 1u); + ASSERT_EQ(registry.current(e1), 3u); +} + +TEST(RegistryDeathTest, DestroyVersion) { + entt::registry registry; const auto entity = registry.create(); + registry.destroy(entity); - ASSERT_EQ(registry.version(entity), entt::registry::version_type{}); + ASSERT_DEATH(registry.destroy(entity), ""); + ASSERT_DEATH(registry.destroy(entity, 3), ""); +} - for(auto i = entt::entt_traits::version_mask; i; --i) { - ASSERT_NE(registry.current(entity), registry.version(entity)); - registry.destroy(registry.create()); - } +TEST(Registry, RangeDestroy) { + entt::registry registry; + const auto iview = registry.view(); + const auto icview = registry.view(); + entt::entity entities[3u]; + + registry.create(std::begin(entities), std::end(entities)); + + registry.emplace(entities[0u]); + registry.emplace(entities[0u]); + registry.emplace(entities[0u]); + + registry.emplace(entities[1u]); + registry.emplace(entities[1u]); + + registry.emplace(entities[2u]); + + ASSERT_TRUE(registry.valid(entities[0u])); + ASSERT_TRUE(registry.valid(entities[1u])); + ASSERT_TRUE(registry.valid(entities[2u])); + + registry.destroy(icview.begin(), icview.end()); + + ASSERT_FALSE(registry.valid(entities[0u])); + ASSERT_FALSE(registry.valid(entities[1u])); + ASSERT_TRUE(registry.valid(entities[2u])); + + ASSERT_EQ(registry.storage().size(), 1u); + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 0u); + + registry.destroy(iview.begin(), iview.end()); + + ASSERT_FALSE(registry.valid(entities[2u])); + ASSERT_NO_FATAL_FAILURE(registry.destroy(iview.rbegin(), iview.rend())); + ASSERT_EQ(iview.size(), 0u); + ASSERT_EQ(icview.size_hint(), 0u); + + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 0u); + + registry.create(std::begin(entities), std::end(entities)); + registry.insert(std::begin(entities), std::end(entities)); + + ASSERT_TRUE(registry.valid(entities[0u])); + ASSERT_TRUE(registry.valid(entities[1u])); + ASSERT_TRUE(registry.valid(entities[2u])); + ASSERT_EQ(registry.storage().size(), 3u); + + registry.destroy(std::begin(entities), std::end(entities)); + + ASSERT_FALSE(registry.valid(entities[0u])); + ASSERT_FALSE(registry.valid(entities[1u])); + ASSERT_FALSE(registry.valid(entities[2u])); + ASSERT_EQ(registry.storage().size(), 0u); +} + +TEST(Registry, StableDestroy) { + entt::registry registry; + const auto iview = registry.view(); + const auto icview = registry.view(); + entt::entity entities[3u]; + + registry.create(std::begin(entities), std::end(entities)); + + registry.emplace(entities[0u]); + registry.emplace(entities[0u]); + registry.emplace(entities[0u]); + + registry.emplace(entities[1u]); + registry.emplace(entities[1u]); + + registry.emplace(entities[2u]); + + ASSERT_TRUE(registry.valid(entities[0u])); + ASSERT_TRUE(registry.valid(entities[1u])); + ASSERT_TRUE(registry.valid(entities[2u])); + + registry.destroy(icview.begin(), icview.end()); + + ASSERT_FALSE(registry.valid(entities[0u])); + ASSERT_FALSE(registry.valid(entities[1u])); + ASSERT_TRUE(registry.valid(entities[2u])); + + ASSERT_EQ(registry.storage().size(), 1u); + ASSERT_EQ(registry.storage().size(), 2u); + ASSERT_EQ(registry.storage().size(), 0u); + + registry.destroy(iview.begin(), iview.end()); + + ASSERT_FALSE(registry.valid(entities[2u])); + ASSERT_EQ(iview.size(), 0u); + ASSERT_EQ(icview.size_hint(), 0u); + + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 2u); + ASSERT_EQ(registry.storage().size(), 0u); +} + +TEST(Registry, ReleaseVersion) { + entt::registry registry; + entt::entity entities[2u]; + + registry.create(std::begin(entities), std::end(entities)); + + ASSERT_EQ(registry.current(entities[0u]), 0u); + ASSERT_EQ(registry.current(entities[1u]), 0u); + + registry.release(entities[0u]); + registry.release(entities[1u], 3); + + ASSERT_EQ(registry.current(entities[0u]), 1u); + ASSERT_EQ(registry.current(entities[1u]), 3u); +} + +TEST(RegistryDeathTest, ReleaseVersion) { + entt::registry registry; + entt::entity entity = registry.create(); + + registry.release(entity); + + ASSERT_DEATH(registry.release(entity), ""); + ASSERT_DEATH(registry.release(entity, 3), ""); +} + +TEST(Registry, RangeRelease) { + entt::registry registry; + entt::entity entities[3u]; + + registry.create(std::begin(entities), std::end(entities)); + + ASSERT_TRUE(registry.valid(entities[0u])); + ASSERT_TRUE(registry.valid(entities[1u])); + ASSERT_TRUE(registry.valid(entities[2u])); + + registry.release(std::begin(entities), std::end(entities) - 1u); - ASSERT_EQ(registry.current(entity), registry.version(entity)); + ASSERT_FALSE(registry.valid(entities[0u])); + ASSERT_FALSE(registry.valid(entities[1u])); + ASSERT_TRUE(registry.valid(entities[2u])); + + registry.release(std::end(entities) - 1u, std::end(entities)); + + ASSERT_FALSE(registry.valid(entities[2u])); +} + +TEST(Registry, VersionOverflow) { + using traits_type = entt::entt_traits; + + entt::registry registry; + const auto entity = registry.create(); + + registry.release(entity); + + ASSERT_NE(registry.current(entity), traits_type::to_version(entity)); + ASSERT_NE(registry.current(entity), typename traits_type::version_type{}); + + registry.release(registry.create(), traits_type::to_version(entt::tombstone) - 1u); + registry.release(registry.create()); + + ASSERT_EQ(registry.current(entity), traits_type::to_version(entity)); + ASSERT_EQ(registry.current(entity), typename traits_type::version_type{}); +} + +TEST(Registry, NullEntity) { + entt::registry registry; + const entt::entity entity = entt::null; + + ASSERT_FALSE(registry.valid(entity)); + ASSERT_NE(registry.create(entity), entity); +} + +TEST(Registry, TombstoneVersion) { + using traits_type = entt::entt_traits; + + entt::registry registry; + const entt::entity entity = entt::tombstone; + + ASSERT_FALSE(registry.valid(entity)); + + const auto other = registry.create(); + const auto vers = traits_type::to_version(entity); + const auto required = traits_type::construct(traits_type::to_entity(other), vers); + + ASSERT_NE(registry.release(other, vers), vers); + ASSERT_NE(registry.create(required), required); } TEST(Registry, Each) { @@ -324,18 +788,18 @@ TEST(Registry, Each) { entt::registry::size_type tot; entt::registry::size_type match; - registry.create(); - registry.assign(registry.create()); - registry.create(); - registry.assign(registry.create()); - registry.create(); + static_cast(registry.create()); + registry.emplace(registry.create()); + static_cast(registry.create()); + registry.emplace(registry.create()); + static_cast(registry.create()); tot = 0u; match = 0u; registry.each([&](auto entity) { - if(registry.has(entity)) { ++match; } - registry.create(); + if(registry.all_of(entity)) { ++match; } + static_cast(registry.create()); ++tot; }); @@ -346,7 +810,7 @@ TEST(Registry, Each) { match = 0u; registry.each([&](auto entity) { - if(registry.has(entity)) { + if(registry.all_of(entity)) { registry.destroy(entity); ++match; } @@ -361,7 +825,7 @@ TEST(Registry, Each) { match = 0u; registry.each([&](auto entity) { - if(registry.has(entity)) { ++match; } + if(registry.all_of(entity)) { ++match; } registry.destroy(entity); ++tot; }); @@ -374,55 +838,22 @@ TEST(Registry, Each) { TEST(Registry, Orphans) { entt::registry registry; - entt::registry::size_type tot{}; - - registry.assign(registry.create()); - registry.create(); - registry.assign(registry.create()); - - registry.orphans([&](auto) { ++tot; }); - ASSERT_EQ(tot, 1u); - tot = {}; - - registry.each([&](auto entity) { registry.reset(entity); }); - registry.orphans([&](auto) { ++tot; }); - ASSERT_EQ(tot, 3u); - registry.reset(); - tot = {}; - - registry.orphans([&](auto) { ++tot; }); - ASSERT_EQ(tot, 0u); -} - -TEST(Registry, CreateDestroyEntities) { - entt::registry registry; - entt::entity pre{}, post{}; - - for(int i = 0; i < 10; ++i) { - const auto entity = registry.create(); - registry.assign(entity); - } - - registry.reset(); + entt::entity entities[3u]{}; - for(int i = 0; i < 7; ++i) { - const auto entity = registry.create(); - registry.assign(entity); - if(i == 3) { pre = entity; } - } + registry.create(std::begin(entities), std::end(entities)); + registry.emplace(entities[0u]); + registry.emplace(entities[2u]); - registry.reset(); + registry.each([&](const auto entt) { + ASSERT_TRUE(entt != entities[1u] || registry.orphan(entt)); + }); - for(int i = 0; i < 5; ++i) { - const auto entity = registry.create(); - if(i == 3) { post = entity; } - } + registry.erase(entities[0u]); + registry.erase(entities[2u]); - ASSERT_FALSE(registry.valid(pre)); - ASSERT_TRUE(registry.valid(post)); - ASSERT_NE(registry.version(pre), registry.version(post)); - ASSERT_EQ(registry.version(pre) + 1, registry.version(post)); - ASSERT_EQ(registry.current(pre), registry.current(post)); + registry.each([&](const auto entt) { + ASSERT_TRUE(registry.orphan(entt)); + }); } TEST(Registry, View) { @@ -430,306 +861,362 @@ TEST(Registry, View) { auto mview = registry.view(); auto iview = registry.view(); auto cview = registry.view(); + entt::entity entities[3u]; - const auto e0 = registry.create(); - registry.assign(e0, 0); - registry.assign(e0, 'c'); + registry.create(std::begin(entities), std::end(entities)); - const auto e1 = registry.create(); - registry.assign(e1, 0); + registry.emplace(entities[0u], 0); + registry.emplace(entities[0u], 'c'); - const auto e2 = registry.create(); - registry.assign(e2, 0); - registry.assign(e2, 'c'); + registry.emplace(entities[1u], 0); - ASSERT_EQ(iview.size(), decltype(iview)::size_type{3}); - ASSERT_EQ(cview.size(), decltype(cview)::size_type{2}); + registry.emplace(entities[2u], 0); + registry.emplace(entities[2u], 'c'); - decltype(mview)::size_type cnt{0}; + ASSERT_EQ(iview.size(), 3u); + ASSERT_EQ(cview.size(), 2u); + + std::size_t cnt{}; mview.each([&cnt](auto...) { ++cnt; }); - ASSERT_EQ(cnt, decltype(mview)::size_type{2}); + ASSERT_EQ(cnt, 2u); } TEST(Registry, NonOwningGroupInitOnFirstUse) { entt::registry registry; + entt::entity entities[3u]; - const auto e0 = registry.create(); - registry.assign(e0, 0); - registry.assign(e0, 'c'); - - const auto e1 = registry.create(); - registry.assign(e1, 0); - - const auto e2 = registry.create(); - registry.assign(e2, 0); - registry.assign(e2, 'c'); - - ASSERT_FALSE(registry.owned()); - ASSERT_FALSE(registry.owned()); + registry.create(std::begin(entities), std::end(entities)); + registry.insert(std::begin(entities), std::end(entities), 0); + registry.emplace(entities[0u], 'c'); + registry.emplace(entities[2u], 'c'); + std::size_t cnt{}; auto group = registry.group<>(entt::get); - decltype(group)::size_type cnt{0}; group.each([&cnt](auto...) { ++cnt; }); - ASSERT_FALSE(registry.owned()); - ASSERT_FALSE(registry.owned()); - ASSERT_EQ(cnt, decltype(group)::size_type{2}); + ASSERT_FALSE((registry.owned())); + ASSERT_EQ(cnt, 2u); } -TEST(Registry, NonOwningGroupInitOnAssign) { - entt::registry registry; - auto group = registry.group<>(entt::get); - - const auto e0 = registry.create(); - registry.assign(e0, 0); - registry.assign(e0, 'c'); - - const auto e1 = registry.create(); - registry.assign(e1, 0); - - const auto e2 = registry.create(); - registry.assign(e2, 0); - registry.assign(e2, 'c'); +TEST(Registry, NonOwningGroupInitOnEmplace) { + entt::registry registry; + entt::entity entities[3u]; + auto group = registry.group<>(entt::get); - ASSERT_FALSE(registry.owned()); - ASSERT_FALSE(registry.owned()); + registry.create(std::begin(entities), std::end(entities)); + registry.insert(std::begin(entities), std::end(entities), 0); + registry.emplace(entities[0u], 'c'); + registry.emplace(entities[2u], 'c'); - decltype(group)::size_type cnt{0}; + std::size_t cnt{}; group.each([&cnt](auto...) { ++cnt; }); - ASSERT_FALSE(registry.owned()); - ASSERT_FALSE(registry.owned()); - ASSERT_EQ(cnt, decltype(group)::size_type{2}); + ASSERT_FALSE((registry.owned())); + ASSERT_EQ(cnt, 2u); } TEST(Registry, FullOwningGroupInitOnFirstUse) { entt::registry registry; + entt::entity entities[3u]; - const auto e0 = registry.create(); - registry.assign(e0, 0); - registry.assign(e0, 'c'); - - const auto e1 = registry.create(); - registry.assign(e1, 0); - - const auto e2 = registry.create(); - registry.assign(e2, 0); - registry.assign(e2, 'c'); - - ASSERT_FALSE(registry.owned()); - ASSERT_FALSE(registry.owned()); + registry.create(std::begin(entities), std::end(entities)); + registry.insert(std::begin(entities), std::end(entities), 0); + registry.emplace(entities[0u], 'c'); + registry.emplace(entities[2u], 'c'); + std::size_t cnt{}; auto group = registry.group(); - decltype(group)::size_type cnt{0}; group.each([&cnt](auto...) { ++cnt; }); ASSERT_TRUE(registry.owned()); ASSERT_TRUE(registry.owned()); - ASSERT_EQ(cnt, decltype(group)::size_type{2}); + ASSERT_FALSE(registry.owned()); + ASSERT_EQ(cnt, 2u); } -TEST(Registry, FullOwningGroupInitOnAssign) { +TEST(Registry, FullOwningGroupInitOnEmplace) { entt::registry registry; + entt::entity entities[3u]; auto group = registry.group(); - const auto e0 = registry.create(); - registry.assign(e0, 0); - registry.assign(e0, 'c'); - - const auto e1 = registry.create(); - registry.assign(e1, 0); - - const auto e2 = registry.create(); - registry.assign(e2, 0); - registry.assign(e2, 'c'); - - ASSERT_TRUE(registry.owned()); - ASSERT_TRUE(registry.owned()); + registry.create(std::begin(entities), std::end(entities)); + registry.insert(std::begin(entities), std::end(entities), 0); + registry.emplace(entities[0u], 'c'); + registry.emplace(entities[2u], 'c'); - decltype(group)::size_type cnt{0}; + std::size_t cnt{}; group.each([&cnt](auto...) { ++cnt; }); ASSERT_TRUE(registry.owned()); ASSERT_TRUE(registry.owned()); - ASSERT_EQ(cnt, decltype(group)::size_type{2}); + ASSERT_FALSE(registry.owned()); + ASSERT_EQ(cnt, 2u); } TEST(Registry, PartialOwningGroupInitOnFirstUse) { entt::registry registry; + entt::entity entities[3u]; - const auto e0 = registry.create(); - registry.assign(e0, 0); - registry.assign(e0, 'c'); - - const auto e1 = registry.create(); - registry.assign(e1, 1); - - const auto e2 = registry.create(); - registry.assign(e2, 2); - registry.assign(e2, 'c'); - - ASSERT_FALSE(registry.owned()); - ASSERT_FALSE(registry.owned()); + registry.create(std::begin(entities), std::end(entities)); + registry.insert(std::begin(entities), std::end(entities), 0); + registry.emplace(entities[0u], 'c'); + registry.emplace(entities[2u], 'c'); + std::size_t cnt{}; auto group = registry.group(entt::get); - decltype(group)::size_type cnt{0}; group.each([&cnt](auto...) { ++cnt; }); + ASSERT_TRUE((registry.owned())); ASSERT_TRUE(registry.owned()); ASSERT_FALSE(registry.owned()); - ASSERT_EQ(cnt, decltype(group)::size_type{2}); - + ASSERT_EQ(cnt, 2u); } -TEST(Registry, PartialOwningGroupInitOnAssign) { +TEST(Registry, PartialOwningGroupInitOnEmplace) { entt::registry registry; + entt::entity entities[3u]; auto group = registry.group(entt::get); - const auto e0 = registry.create(); - registry.assign(e0, 0); - registry.assign(e0, 'c'); - - const auto e1 = registry.create(); - registry.assign(e1, 0); - - const auto e2 = registry.create(); - registry.assign(e2, 0); - registry.assign(e2, 'c'); - - ASSERT_TRUE(registry.owned()); - ASSERT_FALSE(registry.owned()); + registry.create(std::begin(entities), std::end(entities)); + registry.insert(std::begin(entities), std::end(entities), 0); + registry.emplace(entities[0u], 'c'); + registry.emplace(entities[2u], 'c'); - decltype(group)::size_type cnt{0}; + std::size_t cnt{}; group.each([&cnt](auto...) { ++cnt; }); + ASSERT_TRUE((registry.owned())); ASSERT_TRUE(registry.owned()); ASSERT_FALSE(registry.owned()); - ASSERT_EQ(cnt, decltype(group)::size_type{2}); + ASSERT_EQ(cnt, 2u); } -TEST(Registry, CleanViewAfterReset) { +TEST(Registry, CleanViewAfterRemoveAndClear) { entt::registry registry; auto view = registry.view(); const auto entity = registry.create(); - registry.assign(entity, 0); - registry.assign(entity, 'c'); + registry.emplace(entity); + registry.emplace(entity); - ASSERT_EQ(view.size(), entt::registry::size_type{1}); + ASSERT_EQ(view.size_hint(), 1u); - registry.reset(entity); + registry.erase(entity); - ASSERT_EQ(view.size(), entt::registry::size_type{0}); + ASSERT_EQ(view.size_hint(), 1u); - registry.assign(entity, 'c'); + registry.emplace(entity); - ASSERT_EQ(view.size(), entt::registry::size_type{1}); + ASSERT_EQ(view.size_hint(), 1u); - registry.reset(); + registry.clear(); - ASSERT_EQ(view.size(), entt::registry::size_type{0}); + ASSERT_EQ(view.size_hint(), 0u); - registry.assign(entity, 0); + registry.emplace(entity); - ASSERT_EQ(view.size(), entt::registry::size_type{1}); + ASSERT_EQ(view.size_hint(), 1u); - registry.reset(); + registry.clear(); - ASSERT_EQ(view.size(), entt::registry::size_type{0}); + ASSERT_EQ(view.size_hint(), 0u); } -TEST(Registry, CleanNonOwningGroupViewAfterReset) { +TEST(Registry, CleanNonOwningGroupViewAfterRemoveAndClear) { entt::registry registry; auto group = registry.group<>(entt::get); const auto entity = registry.create(); - registry.assign(entity, 0); - registry.assign(entity, 'c'); + registry.emplace(entity, 0); + registry.emplace(entity, 'c'); - ASSERT_EQ(group.size(), entt::registry::size_type{1}); + ASSERT_EQ(group.size(), 1u); - registry.reset(entity); + registry.erase(entity); - ASSERT_EQ(group.size(), entt::registry::size_type{0}); + ASSERT_EQ(group.size(), 0u); - registry.assign(entity, 'c'); + registry.emplace(entity, 'c'); - ASSERT_EQ(group.size(), entt::registry::size_type{1}); + ASSERT_EQ(group.size(), 1u); - registry.reset(); + registry.clear(); - ASSERT_EQ(group.size(), entt::registry::size_type{0}); + ASSERT_EQ(group.size(), 0u); - registry.assign(entity, 0); + registry.emplace(entity, 0); - ASSERT_EQ(group.size(), entt::registry::size_type{1}); + ASSERT_EQ(group.size(), 1u); - registry.reset(); + registry.clear(); - ASSERT_EQ(group.size(), entt::registry::size_type{0}); + ASSERT_EQ(group.size(), 0u); } -TEST(Registry, CleanFullOwningGroupViewAfterReset) { +TEST(Registry, CleanFullOwningGroupViewAfterRemoveAndClear) { entt::registry registry; auto group = registry.group(); const auto entity = registry.create(); - registry.assign(entity, 0); - registry.assign(entity, 'c'); + registry.emplace(entity, 0); + registry.emplace(entity, 'c'); - ASSERT_EQ(group.size(), entt::registry::size_type{1}); + ASSERT_EQ(group.size(), 1u); - registry.reset(entity); + registry.erase(entity); - ASSERT_EQ(group.size(), entt::registry::size_type{0}); + ASSERT_EQ(group.size(), 0u); - registry.assign(entity, 'c'); + registry.emplace(entity, 'c'); - ASSERT_EQ(group.size(), entt::registry::size_type{1}); + ASSERT_EQ(group.size(), 1u); - registry.reset(); + registry.clear(); - ASSERT_EQ(group.size(), entt::registry::size_type{0}); + ASSERT_EQ(group.size(), 0u); - registry.assign(entity, 0); + registry.emplace(entity, 0); - ASSERT_EQ(group.size(), entt::registry::size_type{1}); + ASSERT_EQ(group.size(), 1u); - registry.reset(); + registry.clear(); - ASSERT_EQ(group.size(), entt::registry::size_type{0}); + ASSERT_EQ(group.size(), 0u); } -TEST(Registry, CleanPartialOwningGroupViewAfterReset) { +TEST(Registry, CleanPartialOwningGroupViewAfterRemoveAndClear) { entt::registry registry; auto group = registry.group(entt::get); const auto entity = registry.create(); - registry.assign(entity, 0); - registry.assign(entity, 'c'); + registry.emplace(entity, 0); + registry.emplace(entity, 'c'); + + ASSERT_EQ(group.size(), 1u); + + registry.erase(entity); + + ASSERT_EQ(group.size(), 0u); + + registry.emplace(entity, 'c'); + + ASSERT_EQ(group.size(), 1u); + + registry.clear(); + + ASSERT_EQ(group.size(), 0u); - ASSERT_EQ(group.size(), entt::registry::size_type{1}); + registry.emplace(entity, 0); - registry.reset(entity); + ASSERT_EQ(group.size(), 1u); - ASSERT_EQ(group.size(), entt::registry::size_type{0}); + registry.clear(); - registry.assign(entity, 'c'); + ASSERT_EQ(group.size(), 0u); +} + +TEST(Registry, NestedGroups) { + entt::registry registry; + entt::entity entities[10]; + + registry.create(std::begin(entities), std::end(entities)); + registry.insert(std::begin(entities), std::end(entities)); + registry.insert(std::begin(entities), std::end(entities)); + const auto g1 = registry.group(entt::get, entt::exclude); + + ASSERT_TRUE(registry.sortable(g1)); + ASSERT_EQ(g1.size(), 10u); + + const auto g2 = registry.group(entt::get); + + ASSERT_TRUE(registry.sortable(g1)); + ASSERT_FALSE(registry.sortable(g2)); + ASSERT_EQ(g1.size(), 10u); + ASSERT_EQ(g2.size(), 10u); + + for(auto i = 0u; i < 5u; ++i) { + ASSERT_TRUE(g1.contains(entities[i * 2 + 1])); + ASSERT_TRUE(g1.contains(entities[i * 2])); + ASSERT_TRUE(g2.contains(entities[i * 2 + 1])); + ASSERT_TRUE(g2.contains(entities[i * 2])); + registry.emplace(entities[i * 2]); + } + + ASSERT_EQ(g1.size(), 5u); + ASSERT_EQ(g2.size(), 10u); + + for(auto i = 0u; i < 5u; ++i) { + ASSERT_TRUE(g1.contains(entities[i * 2 + 1])); + ASSERT_FALSE(g1.contains(entities[i * 2])); + ASSERT_TRUE(g2.contains(entities[i * 2 + 1])); + ASSERT_TRUE(g2.contains(entities[i * 2])); + registry.erase(entities[i * 2 + 1]); + } + + ASSERT_EQ(g1.size(), 0u); + ASSERT_EQ(g2.size(), 5u); + + const auto g3 = registry.group(entt::get, entt::exclude); + + ASSERT_FALSE(registry.sortable(g1)); + ASSERT_FALSE(registry.sortable(g2)); + ASSERT_TRUE(registry.sortable(g3)); - ASSERT_EQ(group.size(), entt::registry::size_type{1}); + ASSERT_EQ(g1.size(), 0u); + ASSERT_EQ(g2.size(), 5u); + ASSERT_EQ(g3.size(), 0u); - registry.reset(); + for(auto i = 0u; i < 5u; ++i) { + ASSERT_FALSE(g1.contains(entities[i * 2 + 1])); + ASSERT_FALSE(g1.contains(entities[i * 2])); + ASSERT_FALSE(g2.contains(entities[i * 2 + 1])); + ASSERT_TRUE(g2.contains(entities[i * 2])); + ASSERT_FALSE(g3.contains(entities[i * 2 + 1])); + ASSERT_FALSE(g3.contains(entities[i * 2])); + registry.emplace(entities[i * 2 + 1]); + } - ASSERT_EQ(group.size(), entt::registry::size_type{0}); + ASSERT_EQ(g1.size(), 5u); + ASSERT_EQ(g2.size(), 10u); + ASSERT_EQ(g3.size(), 0u); + + for(auto i = 0u; i < 5u; ++i) { + ASSERT_TRUE(g1.contains(entities[i * 2 + 1])); + ASSERT_FALSE(g1.contains(entities[i * 2])); + ASSERT_TRUE(g2.contains(entities[i * 2 + 1])); + ASSERT_TRUE(g2.contains(entities[i * 2])); + ASSERT_FALSE(g3.contains(entities[i * 2 + 1])); + ASSERT_FALSE(g3.contains(entities[i * 2])); + registry.emplace(entities[i * 2]); + } - registry.assign(entity, 0); + ASSERT_EQ(g1.size(), 5u); + ASSERT_EQ(g2.size(), 10u); + ASSERT_EQ(g3.size(), 0u); - ASSERT_EQ(group.size(), entt::registry::size_type{1}); + for(auto i = 0u; i < 5u; ++i) { + registry.erase(entities[i * 2]); + } - registry.reset(); + ASSERT_EQ(g1.size(), 10u); + ASSERT_EQ(g2.size(), 10u); + ASSERT_EQ(g3.size(), 5u); + + for(auto i = 0u; i < 5u; ++i) { + ASSERT_TRUE(g1.contains(entities[i * 2 + 1])); + ASSERT_TRUE(g1.contains(entities[i * 2])); + ASSERT_TRUE(g2.contains(entities[i * 2 + 1])); + ASSERT_TRUE(g2.contains(entities[i * 2])); + ASSERT_FALSE(g3.contains(entities[i * 2 + 1])); + ASSERT_TRUE(g3.contains(entities[i * 2])); + registry.erase(entities[i * 2 + 1]); + registry.erase(entities[i * 2]); + } - ASSERT_EQ(group.size(), entt::registry::size_type{0}); + ASSERT_EQ(g1.size(), 0u); + ASSERT_EQ(g2.size(), 0u); + ASSERT_EQ(g3.size(), 0u); } TEST(Registry, SortSingle) { @@ -737,9 +1224,9 @@ TEST(Registry, SortSingle) { int val = 0; - registry.assign(registry.create(), val++); - registry.assign(registry.create(), val++); - registry.assign(registry.create(), val++); + registry.emplace(registry.create(), val++); + registry.emplace(registry.create(), val++); + registry.emplace(registry.create(), val++); for(auto entity: registry.view()) { ASSERT_EQ(registry.get(entity), --val); @@ -760,8 +1247,8 @@ TEST(Registry, SortMulti) { for(auto i = 0; i < 3; ++i) { const auto entity = registry.create(); - registry.assign(entity, uval++); - registry.assign(entity, ival++); + registry.emplace(entity, uval++); + registry.emplace(entity, ival++); } for(auto entity: registry.view()) { @@ -784,11 +1271,27 @@ TEST(Registry, SortMulti) { } } +TEST(Registry, SortEmpty) { + entt::registry registry; + + registry.emplace(registry.create()); + registry.emplace(registry.create()); + registry.emplace(registry.create()); + + ASSERT_LT(registry.storage().data()[0], registry.storage().data()[1]); + ASSERT_LT(registry.storage().data()[1], registry.storage().data()[2]); + + registry.sort(std::less{}); + + ASSERT_GT(registry.storage().data()[0], registry.storage().data()[1]); + ASSERT_GT(registry.storage().data()[1], registry.storage().data()[2]); +} + TEST(Registry, ComponentsWithTypesFromStandardTemplateLibrary) { // see #37 - the test shouldn't crash, that's all entt::registry registry; const auto entity = registry.create(); - registry.assign>(entity).insert(42); + registry.emplace>(entity).insert(42); registry.destroy(entity); } @@ -796,339 +1299,416 @@ TEST(Registry, ConstructWithComponents) { // it should compile, that's all entt::registry registry; const auto value = 0; - registry.assign(registry.create(), value); -} - -TEST(Registry, MergeTwoRegistries) { - entt::registry src; - entt::registry dst; - - std::unordered_map ref; - - auto merge = [&ref, &dst](const auto &view) { - view.each([&](auto entity, const auto &component) { - if(ref.find(entity) == ref.cend()) { - const auto other = dst.create(); - dst.template assign>(other, component); - ref.emplace(entity, other); - } else { - using component_type = std::decay_t; - dst.template assign(ref[entity], component); - } - }); - }; - - auto e0 = src.create(); - src.assign(e0); - src.assign(e0); - src.assign(e0); - - auto e1 = src.create(); - src.assign(e1); - src.assign(e1); - src.assign(e1); - - auto e2 = dst.create(); - dst.assign(e2); - dst.assign(e2); - dst.assign(e2); - - auto e3 = dst.create(); - dst.assign(e3); - dst.assign(e3); - - auto eq = [](auto begin, auto end) { ASSERT_EQ(begin, end); }; - auto ne = [](auto begin, auto end) { ASSERT_NE(begin, end); }; - - eq(dst.view().begin(), dst.view().end()); - eq(dst.view().begin(), dst.view().end()); - - merge(src.view()); - merge(src.view()); - merge(src.view()); - merge(src.view()); - - ne(dst.view().begin(), dst.view().end()); - ne(dst.view().begin(), dst.view().end()); + registry.emplace(registry.create(), value); } TEST(Registry, Signals) { entt::registry registry; + entt::entity entities[2u]; listener listener; - registry.on_construct().connect<&listener::incr>(&listener); - registry.on_destroy().connect<&listener::decr>(&listener); - registry.on_construct().connect<&listener::incr>(&listener); - registry.on_destroy().connect<&listener::decr>(&listener); + registry.on_construct().connect<&listener::incr>(listener); + registry.on_destroy().connect<&listener::decr>(listener); + registry.on_construct().connect<&listener::incr>(listener); + registry.on_destroy().connect<&listener::decr>(listener); - auto e0 = registry.create(); - auto e1 = registry.create(); - - registry.assign(e0); - registry.assign(e1); + registry.create(std::begin(entities), std::end(entities)); + registry.insert(std::begin(entities), std::end(entities)); ASSERT_EQ(listener.counter, 2); - ASSERT_EQ(listener.last, e1); + ASSERT_EQ(listener.last, entities[1u]); - registry.assign(e1); - registry.assign(e0); + registry.insert(std::rbegin(entities), std::rend(entities)); ASSERT_EQ(listener.counter, 4); - ASSERT_EQ(listener.last, e0); + ASSERT_EQ(listener.last, entities[0u]); - registry.remove(e0); - registry.remove(e0); + registry.erase(entities[0u]); ASSERT_EQ(listener.counter, 2); - ASSERT_EQ(listener.last, e0); + ASSERT_EQ(listener.last, entities[0u]); - registry.on_destroy().disconnect<&listener::decr>(&listener); - registry.on_destroy().disconnect<&listener::decr>(&listener); + registry.on_destroy().disconnect<&listener::decr>(listener); + registry.on_destroy().disconnect<&listener::decr>(listener); - registry.remove(e1); - registry.remove(e1); + registry.erase(entities[1u]); ASSERT_EQ(listener.counter, 2); - ASSERT_EQ(listener.last, e0); + ASSERT_EQ(listener.last, entities[0u]); - registry.on_construct().disconnect<&listener::incr>(&listener); - registry.on_construct().disconnect<&listener::incr>(&listener); + registry.on_construct().disconnect<&listener::incr>(listener); + registry.on_construct().disconnect<&listener::incr>(listener); - registry.assign(e1); - registry.assign(e1); + registry.emplace(entities[1u]); + registry.emplace(entities[1u]); ASSERT_EQ(listener.counter, 2); - ASSERT_EQ(listener.last, e0); + ASSERT_EQ(listener.last, entities[0u]); - registry.on_construct().connect<&listener::incr>(&listener); - registry.on_destroy().connect<&listener::decr>(&listener); + registry.on_construct().connect<&listener::incr>(listener); + registry.on_destroy().connect<&listener::decr>(listener); - registry.assign(e0); - registry.reset(e1); + registry.emplace(entities[0u]); + registry.erase(entities[1u]); ASSERT_EQ(listener.counter, 2); - ASSERT_EQ(listener.last, e1); + ASSERT_EQ(listener.last, entities[1u]); - registry.on_construct().connect<&listener::incr>(&listener); - registry.on_destroy().connect<&listener::decr>(&listener); + registry.on_construct().connect<&listener::incr>(listener); + registry.on_destroy().connect<&listener::decr>(listener); - registry.reset(e1); - registry.assign(e0); + registry.erase(entities[1u]); + registry.emplace(entities[0u]); ASSERT_EQ(listener.counter, 2); - ASSERT_EQ(listener.last, e0); + ASSERT_EQ(listener.last, entities[0u]); - registry.reset(); - registry.reset(); + registry.clear(); ASSERT_EQ(listener.counter, 0); - ASSERT_EQ(listener.last, e0); + ASSERT_EQ(listener.last, entities[0u]); - registry.assign(e0); - registry.assign(e1); - registry.assign(e0); - registry.assign(e1); - - registry.destroy(e1); + registry.insert(std::begin(entities), std::end(entities)); + registry.insert(std::begin(entities), std::end(entities)); + registry.destroy(entities[1u]); ASSERT_EQ(listener.counter, 2); - ASSERT_EQ(listener.last, e1); + ASSERT_EQ(listener.last, entities[1u]); - registry.remove(e0); - registry.remove(e0); - registry.assign_or_replace(e0); - registry.assign_or_replace(e0); + registry.erase(entities[0u]); + registry.emplace_or_replace(entities[0u]); + registry.emplace_or_replace(entities[0u]); ASSERT_EQ(listener.counter, 2); - ASSERT_EQ(listener.last, e0); + ASSERT_EQ(listener.last, entities[0u]); - registry.on_destroy().disconnect<&listener::decr>(&listener); - registry.on_destroy().disconnect<&listener::decr>(&listener); + registry.on_destroy().disconnect<&listener::decr>(listener); + registry.on_destroy().disconnect<&listener::decr>(listener); - registry.assign_or_replace(e0); - registry.assign_or_replace(e0); + registry.emplace_or_replace(entities[0u]); + registry.emplace_or_replace(entities[0u]); ASSERT_EQ(listener.counter, 2); - ASSERT_EQ(listener.last, e0); + ASSERT_EQ(listener.last, entities[0u]); - registry.on_replace().connect<&listener::incr>(&listener); - registry.on_replace().connect<&listener::incr>(&listener); + registry.on_update().connect<&listener::incr>(listener); + registry.on_update().connect<&listener::incr>(listener); - registry.assign_or_replace(e0); - registry.assign_or_replace(e0); + registry.emplace_or_replace(entities[0u]); + registry.emplace_or_replace(entities[0u]); ASSERT_EQ(listener.counter, 4); - ASSERT_EQ(listener.last, e0); + ASSERT_EQ(listener.last, entities[0u]); - registry.replace(e0); - registry.replace(e0); + registry.replace(entities[0u]); + registry.replace(entities[0u]); ASSERT_EQ(listener.counter, 6); - ASSERT_EQ(listener.last, e0); + ASSERT_EQ(listener.last, entities[0u]); } -TEST(Registry, DestroyByComponents) { +TEST(Registry, SignalWhenDestroying) { entt::registry registry; + const auto entity = registry.create(); - const auto e0 = registry.create(); - const auto e1 = registry.create(); - const auto e2 = registry.create(); + registry.on_destroy().connect<&entt::registry::remove>(); + registry.emplace(entity); + registry.emplace(entity); - registry.assign(e0); - registry.assign(e0); - registry.assign(e0); + ASSERT_NE(registry.storage(entt::type_id().hash()), registry.storage().end()); + ASSERT_NE(registry.storage(entt::type_id().hash()), registry.storage().end()); + ASSERT_EQ(registry.storage(entt::type_id().hash()), registry.storage().end()); + ASSERT_TRUE(registry.valid(entity)); - registry.assign(e1); - registry.assign(e1); + registry.destroy(entity); - registry.assign(e2); + ASSERT_NE(registry.storage(entt::type_id().hash()), registry.storage().end()); + ASSERT_FALSE(registry.valid(entity)); +} - ASSERT_TRUE(registry.valid(e0)); - ASSERT_TRUE(registry.valid(e1)); - ASSERT_TRUE(registry.valid(e2)); +TEST(Registry, Insert) { + entt::registry registry; + entt::entity entities[3u]; - { - const auto view = registry.view(); - registry.destroy(view.begin(), view.end()); - } + registry.create(std::begin(entities), std::end(entities)); - ASSERT_FALSE(registry.valid(e0)); - ASSERT_TRUE(registry.valid(e1)); - ASSERT_TRUE(registry.valid(e2)); + registry.emplace(entities[0u]); + registry.emplace(entities[0u]); + registry.emplace(entities[0u]); - { - const auto view = registry.view(); - registry.destroy(view.begin(), view.end()); - } + registry.emplace(entities[1u]); + registry.emplace(entities[1u]); - ASSERT_FALSE(registry.valid(e0)); - ASSERT_FALSE(registry.valid(e1)); - ASSERT_TRUE(registry.valid(e2)); + registry.emplace(entities[2u]); - { - const auto view = registry.view(); - registry.destroy(view.begin(), view.end()); - } + ASSERT_FALSE(registry.all_of(entities[0u])); + ASSERT_FALSE(registry.all_of(entities[1u])); + ASSERT_FALSE(registry.all_of(entities[2u])); - ASSERT_FALSE(registry.valid(e0)); - ASSERT_FALSE(registry.valid(e1)); - ASSERT_FALSE(registry.valid(e2)); + const auto icview = registry.view(); + registry.insert(icview.begin(), icview.end(), 3.f); + + ASSERT_EQ(registry.get(entities[0u]), 3.f); + ASSERT_EQ(registry.get(entities[1u]), 3.f); + ASSERT_FALSE(registry.all_of(entities[2u])); + + registry.clear(); + float value[3]{0.f, 1.f, 2.f}; + + const auto iview = registry.view(); + registry.insert(iview.rbegin(), iview.rend(), value); + + ASSERT_EQ(registry.get(entities[0u]), 0.f); + ASSERT_EQ(registry.get(entities[1u]), 1.f); + ASSERT_EQ(registry.get(entities[2u]), 2.f); } -TEST(Registry, CreateManyEntitiesAtOnce) { +TEST(Registry, Erase) { entt::registry registry; - entt::entity entities[3]; + const auto iview = registry.view(); + const auto icview = registry.view(); + entt::entity entities[3u]; - const auto entity = registry.create(); - registry.destroy(registry.create()); - registry.destroy(entity); - registry.destroy(registry.create()); + registry.create(std::begin(entities), std::end(entities)); + + registry.emplace(entities[0u]); + registry.emplace(entities[0u]); + registry.emplace(entities[0u]); + + registry.emplace(entities[1u]); + registry.emplace(entities[1u]); + + registry.emplace(entities[2u]); + + ASSERT_TRUE(registry.any_of(entities[0u])); + ASSERT_TRUE(registry.all_of(entities[1u])); + ASSERT_TRUE(registry.any_of(entities[2u])); + + registry.erase(entities[0u]); + registry.erase(icview.begin(), icview.end()); + + ASSERT_FALSE(registry.any_of(entities[0u])); + ASSERT_FALSE(registry.all_of(entities[1u])); + ASSERT_TRUE(registry.any_of(entities[2u])); + + ASSERT_EQ(registry.storage().size(), 1u); + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 1u); + + registry.erase(iview.begin(), iview.end()); + + ASSERT_FALSE(registry.any_of(entities[2u])); + ASSERT_NO_FATAL_FAILURE(registry.erase(iview.rbegin(), iview.rend())); + + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 1u); + + registry.insert(std::begin(entities), std::end(entities)); + registry.insert(std::begin(entities), std::end(entities)); + + ASSERT_EQ(registry.storage().size(), 3u); + ASSERT_EQ(registry.storage().size(), 3u); + + registry.erase(std::begin(entities), std::end(entities)); + + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 0u); + + ASSERT_FALSE(registry.orphan(entities[0u])); + ASSERT_TRUE(registry.orphan(entities[1u])); + ASSERT_TRUE(registry.orphan(entities[2u])); +} + +TEST(RegistryDeathTest, Erase) { + entt::registry registry; + const entt::entity entities[1u]{registry.create()}; + + ASSERT_FALSE((registry.any_of(entities[0u]))); + ASSERT_DEATH((registry.erase(std::begin(entities), std::end(entities))), ""); + ASSERT_DEATH(registry.erase(entities[0u]), ""); +} + +TEST(Registry, StableErase) { + entt::registry registry; + const auto iview = registry.view(); + const auto icview = registry.view(); + entt::entity entities[3u]; registry.create(std::begin(entities), std::end(entities)); - ASSERT_TRUE(registry.valid(entities[0])); - ASSERT_TRUE(registry.valid(entities[1])); - ASSERT_TRUE(registry.valid(entities[2])); + registry.emplace(entities[0u]); + registry.emplace(entities[0u]); + registry.emplace(entities[0u]); + + registry.emplace(entities[1u]); + registry.emplace(entities[1u]); + + registry.emplace(entities[2u]); + + ASSERT_TRUE(registry.any_of(entities[0u])); + ASSERT_TRUE(registry.all_of(entities[1u])); + ASSERT_TRUE(registry.any_of(entities[2u])); + + registry.erase(entities[0u]); + registry.erase(icview.begin(), icview.end()); + registry.erase(icview.begin(), icview.end()); - ASSERT_EQ(registry.entity(entities[0]), entt::entity{0}); - ASSERT_EQ(registry.version(entities[0]), entt::registry::version_type{2}); + ASSERT_FALSE(registry.any_of(entities[0u])); + ASSERT_FALSE(registry.all_of(entities[1u])); + ASSERT_TRUE(registry.any_of(entities[2u])); - ASSERT_EQ(registry.entity(entities[1]), entt::entity{1}); - ASSERT_EQ(registry.version(entities[1]), entt::registry::version_type{1}); + ASSERT_EQ(registry.storage().size(), 1u); + ASSERT_EQ(registry.storage().size(), 2u); + ASSERT_EQ(registry.storage().size(), 1u); - ASSERT_EQ(registry.entity(entities[2]), entt::entity{2}); - ASSERT_EQ(registry.version(entities[2]), entt::registry::version_type{0}); + registry.erase(iview.begin(), iview.end()); + + ASSERT_FALSE(registry.any_of(entities[2u])); + + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 2u); + ASSERT_EQ(registry.storage().size(), 1u); } -TEST(Registry, CreateAnEntityWithComponents) { +TEST(Registry, Remove) { entt::registry registry; - auto &&[entity, ivalue, cvalue, evalue] = registry.create(); - // suppress warnings - (void)evalue; + const auto iview = registry.view(); + const auto icview = registry.view(); + entt::entity entities[3u]; + + registry.create(std::begin(entities), std::end(entities)); + + registry.emplace(entities[0u]); + registry.emplace(entities[0u]); + registry.emplace(entities[0u]); + + registry.emplace(entities[1u]); + registry.emplace(entities[1u]); + + registry.emplace(entities[2u]); + + ASSERT_TRUE(registry.any_of(entities[0u])); + ASSERT_TRUE(registry.all_of(entities[1u])); + ASSERT_TRUE(registry.any_of(entities[2u])); - ASSERT_FALSE(registry.empty()); - ASSERT_FALSE(registry.empty()); - ASSERT_FALSE(registry.empty()); + registry.remove(entities[0u]); - ASSERT_EQ(registry.size(), entt::registry::size_type{1}); - ASSERT_EQ(registry.size(), entt::registry::size_type{1}); - ASSERT_EQ(registry.size(), entt::registry::size_type{1}); + ASSERT_EQ((registry.remove(icview.begin(), icview.end())), 2u); + ASSERT_EQ((registry.remove(icview.begin(), icview.end())), 0u); - ASSERT_TRUE((registry.has(entity))); + ASSERT_FALSE(registry.any_of(entities[0u])); + ASSERT_FALSE(registry.all_of(entities[1u])); + ASSERT_TRUE(registry.any_of(entities[2u])); - ivalue = 42; - cvalue = 'c'; + ASSERT_EQ(registry.storage().size(), 1u); + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 1u); - ASSERT_EQ(registry.get(entity), 42); - ASSERT_EQ(registry.get(entity), 'c'); + ASSERT_EQ((registry.remove(iview.begin(), iview.end())), 1u); + + ASSERT_EQ(registry.remove(entities[0u]), 0u); + ASSERT_EQ(registry.remove(entities[1u]), 0u); + + ASSERT_FALSE(registry.any_of(entities[2u])); + ASSERT_EQ(registry.remove(iview.begin(), iview.end()), 0u); + + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 1u); + + registry.insert(std::begin(entities), std::end(entities)); + registry.insert(std::begin(entities), std::end(entities)); + + ASSERT_EQ(registry.storage().size(), 3u); + ASSERT_EQ(registry.storage().size(), 3u); + + registry.remove(std::begin(entities), std::end(entities)); + registry.remove(std::begin(entities), std::end(entities)); + + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 0u); + + ASSERT_FALSE(registry.orphan(entities[0u])); + ASSERT_TRUE(registry.orphan(entities[1u])); + ASSERT_TRUE(registry.orphan(entities[2u])); } -TEST(Registry, CreateManyEntitiesWithComponentsAtOnce) { +TEST(Registry, StableRemove) { entt::registry registry; - entt::entity entities[3]; + const auto iview = registry.view(); + const auto icview = registry.view(); + entt::entity entities[3u]; - const auto entity = registry.create(); - registry.destroy(registry.create()); - registry.destroy(entity); - registry.destroy(registry.create()); + registry.create(std::begin(entities), std::end(entities)); - const auto [iptr, cptr, eptr] = registry.create(std::begin(entities), std::end(entities)); + registry.emplace(entities[0u]); + registry.emplace(entities[0u]); + registry.emplace(entities[0u]); - ASSERT_FALSE(registry.empty()); - ASSERT_FALSE(registry.empty()); - ASSERT_FALSE(registry.empty()); + registry.emplace(entities[1u]); + registry.emplace(entities[1u]); - ASSERT_NE(iptr, nullptr); - ASSERT_NE(cptr, nullptr); - ASSERT_EQ(eptr, nullptr); + registry.emplace(entities[2u]); - ASSERT_EQ(registry.size(), entt::registry::size_type{3}); - ASSERT_EQ(registry.size(), entt::registry::size_type{3}); - ASSERT_EQ(registry.size(), entt::registry::size_type{3}); + ASSERT_TRUE(registry.any_of(entities[0u])); + ASSERT_TRUE(registry.all_of(entities[1u])); + ASSERT_TRUE(registry.any_of(entities[2u])); - ASSERT_TRUE(registry.valid(entities[0])); - ASSERT_TRUE(registry.valid(entities[1])); - ASSERT_TRUE(registry.valid(entities[2])); + registry.remove(entities[0u]); - ASSERT_EQ(registry.entity(entities[0]), entt::entity{0}); - ASSERT_EQ(registry.version(entities[0]), entt::registry::version_type{2}); + ASSERT_EQ((registry.remove(icview.begin(), icview.end())), 2u); + ASSERT_EQ((registry.remove(icview.begin(), icview.end())), 0u); - ASSERT_EQ(registry.entity(entities[1]), entt::entity{1}); - ASSERT_EQ(registry.version(entities[1]), entt::registry::version_type{1}); + ASSERT_FALSE(registry.any_of(entities[0u])); + ASSERT_FALSE(registry.all_of(entities[1u])); + ASSERT_TRUE(registry.any_of(entities[2u])); - ASSERT_EQ(registry.entity(entities[2]), entt::entity{2}); - ASSERT_EQ(registry.version(entities[2]), entt::registry::version_type{0}); + ASSERT_EQ(registry.storage().size(), 1u); + ASSERT_EQ(registry.storage().size(), 2u); + ASSERT_EQ(registry.storage().size(), 1u); - ASSERT_TRUE((registry.has(entities[0]))); - ASSERT_TRUE((registry.has(entities[1]))); - ASSERT_TRUE((registry.has(entities[2]))); + ASSERT_EQ((registry.remove(iview.begin(), iview.end())), 1u); - for(auto i = 0; i < 3; ++i) { - iptr[i] = i; - cptr[i] = char('a'+i); - } + ASSERT_EQ(registry.remove(entities[0u]), 0u); + ASSERT_EQ(registry.remove(entities[1u]), 0u); - for(auto i = 0; i < 3; ++i) { - ASSERT_EQ(registry.get(entities[i]), i); - ASSERT_EQ(registry.get(entities[i]), char('a'+i)); - } + ASSERT_FALSE(registry.any_of(entities[2u])); + + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 2u); + ASSERT_EQ(registry.storage().size(), 1u); } -TEST(Registry, CreateManyEntitiesWithComponentsAtOnceWithListener) { +TEST(Registry, Compact) { entt::registry registry; - entt::entity entities[3]; - listener listener; + entt::entity entities[2u]; - registry.on_construct().connect<&listener::incr>(&listener); - registry.create(std::begin(entities), std::end(entities)); + registry.create(std::begin(entities), std::end(entities)); - ASSERT_EQ(listener.counter, 3); + registry.emplace(entities[0u]); + registry.emplace(entities[0u]); - registry.on_construct().disconnect<&listener::incr>(&listener); - registry.on_construct().connect<&listener::incr>(&listener); - registry.create(std::begin(entities), std::end(entities)); + registry.emplace(entities[1u]); + registry.emplace(entities[1u]); - ASSERT_EQ(listener.counter, 6); + ASSERT_EQ(registry.storage().size(), 2u); + ASSERT_EQ(registry.storage().size(), 2u); + + registry.destroy(std::begin(entities), std::end(entities)); + + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 2u); + + registry.compact(); + + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 2u); + + registry.compact(); + + ASSERT_EQ(registry.storage().size(), 0u); + ASSERT_EQ(registry.storage().size(), 0u); } TEST(Registry, NonOwningGroupInterleaved) { @@ -1136,19 +1716,19 @@ TEST(Registry, NonOwningGroupInterleaved) { typename entt::entity entity = entt::null; entity = registry.create(); - registry.assign(entity); - registry.assign(entity); + registry.emplace(entity); + registry.emplace(entity); const auto group = registry.group<>(entt::get); entity = registry.create(); - registry.assign(entity); - registry.assign(entity); + registry.emplace(entity); + registry.emplace(entity); - decltype(group)::size_type cnt{0}; + std::size_t cnt{}; group.each([&cnt](auto...) { ++cnt; }); - ASSERT_EQ(cnt, decltype(group)::size_type{2}); + ASSERT_EQ(cnt, 2u); } TEST(Registry, FullOwningGroupInterleaved) { @@ -1156,19 +1736,19 @@ TEST(Registry, FullOwningGroupInterleaved) { typename entt::entity entity = entt::null; entity = registry.create(); - registry.assign(entity); - registry.assign(entity); + registry.emplace(entity); + registry.emplace(entity); const auto group = registry.group(); entity = registry.create(); - registry.assign(entity); - registry.assign(entity); + registry.emplace(entity); + registry.emplace(entity); - decltype(group)::size_type cnt{0}; + std::size_t cnt{}; group.each([&cnt](auto...) { ++cnt; }); - ASSERT_EQ(cnt, decltype(group)::size_type{2}); + ASSERT_EQ(cnt, 2u); } TEST(Registry, PartialOwningGroupInterleaved) { @@ -1176,19 +1756,19 @@ TEST(Registry, PartialOwningGroupInterleaved) { typename entt::entity entity = entt::null; entity = registry.create(); - registry.assign(entity); - registry.assign(entity); + registry.emplace(entity); + registry.emplace(entity); const auto group = registry.group(entt::get); entity = registry.create(); - registry.assign(entity); - registry.assign(entity); + registry.emplace(entity); + registry.emplace(entity); - decltype(group)::size_type cnt{0}; + std::size_t cnt{}; group.each([&cnt](auto...) { ++cnt; }); - ASSERT_EQ(cnt, decltype(group)::size_type{2}); + ASSERT_EQ(cnt, 2u); } TEST(Registry, NonOwningGroupSortInterleaved) { @@ -1196,19 +1776,19 @@ TEST(Registry, NonOwningGroupSortInterleaved) { const auto group = registry.group<>(entt::get); const auto e0 = registry.create(); - registry.assign(e0, 0); - registry.assign(e0, '0'); + registry.emplace(e0, 0); + registry.emplace(e0, '0'); const auto e1 = registry.create(); - registry.assign(e1, 1); - registry.assign(e1, '1'); + registry.emplace(e1, 1); + registry.emplace(e1, '1'); - registry.sort([](auto lhs, auto rhs) { return lhs > rhs; }); - registry.sort([](auto lhs, auto rhs) { return lhs < rhs; }); + registry.sort(std::greater{}); + registry.sort(std::less{}); const auto e2 = registry.create(); - registry.assign(e2, 2); - registry.assign(e2, '2'); + registry.emplace(e2, 2); + registry.emplace(e2, '2'); group.each([e0, e1, e2](const auto entity, const auto &i, const auto &c) { if(entity == e0) { @@ -1224,148 +1804,317 @@ TEST(Registry, NonOwningGroupSortInterleaved) { }); } -TEST(Registry, Clone) { +TEST(Registry, GetOrEmplace) { entt::registry registry; - entt::registry other; + const auto entity = registry.create(); + const auto value = registry.get_or_emplace(entity, 3); + // get_or_emplace must work for empty types + static_cast(registry.get_or_emplace(entity)); - registry.destroy(registry.create()); + ASSERT_TRUE((registry.all_of(entity))); + ASSERT_EQ(registry.get(entity), value); + ASSERT_EQ(registry.get(entity), 3); +} - const auto e0 = registry.create(); - registry.assign(e0, 0); - registry.assign(e0, 0.0); +TEST(Registry, Constness) { + entt::registry registry; - const auto e1 = registry.create(); - registry.assign(e1, 1); - registry.assign(e1, '1'); - registry.assign(e1, 1.1); + static_assert((std::is_same_v({})), int &>)); + static_assert((std::is_same_v({})), void>)); - const auto e2 = registry.create(); - registry.assign(e2, 2); - registry.assign(e2, '2'); + static_assert((std::is_same_v({})), int &>)); + static_assert((std::is_same_v({})), std::tuple>)); + + static_assert((std::is_same_v({})), int *>)); + static_assert((std::is_same_v({})), std::tuple>)); + + static_assert((std::is_same_v()), int &>)); + static_assert((std::is_same_v()), const char &>)); + + static_assert((std::is_same_v()), int *>)); + static_assert((std::is_same_v()), const char *>)); + + static_assert((std::is_same_v({})), const int &>)); + static_assert((std::is_same_v({})), std::tuple>)); + + static_assert((std::is_same_v({})), const int *>)); + static_assert((std::is_same_v({})), std::tuple>)); + + static_assert((std::is_same_v()), const int &>)); + static_assert((std::is_same_v()), const char &>)); + + static_assert((std::is_same_v()), const int *>)); + static_assert((std::is_same_v()), const char *>)); +} - registry.destroy(e1); +TEST(Registry, MoveOnlyComponent) { + entt::registry registry; + // the purpose is to ensure that move only types are always accepted + registry.emplace>(registry.create()); +} - ASSERT_EQ((other.group().size()), entt::registry::size_type{0}); +TEST(Registry, NonDefaultConstructibleComponent) { + entt::registry registry; + // the purpose is to ensure that non default constructible type are always accepted + registry.emplace(registry.create(), 42); +} - other = registry.clone(); +TEST(Registry, Dependencies) { + entt::registry registry; + const auto entity = registry.create(); - ASSERT_EQ((other.group().size()), entt::registry::size_type{1}); - ASSERT_EQ(other.size(), registry.size()); - ASSERT_EQ(other.alive(), registry.alive()); + // required because of an issue of VS2019 + constexpr auto emplace_or_replace = &entt::registry::emplace_or_replace; + constexpr auto remove = &entt::registry::remove; - ASSERT_TRUE(other.valid(e0)); - ASSERT_FALSE(other.valid(e1)); - ASSERT_TRUE(other.valid(e2)); + registry.on_construct().connect(); + registry.on_destroy().connect(); + registry.emplace(entity, .3); - ASSERT_TRUE((other.has(e0))); - ASSERT_FALSE((other.has(e0))); - ASSERT_TRUE((other.has(e2))); + ASSERT_FALSE(registry.all_of(entity)); + ASSERT_EQ(registry.get(entity), .3); - ASSERT_EQ(other.get(e0), 0); - ASSERT_EQ(other.get(e2), 2); - ASSERT_EQ(other.get(e2), '2'); + registry.emplace(entity); - const auto e3 = other.create(); + ASSERT_TRUE(registry.all_of(entity)); + ASSERT_EQ(registry.get(entity), .0); - ASSERT_NE(e1, e3); - ASSERT_EQ(registry.entity(e1), registry.entity(e3)); - ASSERT_EQ(other.entity(e1), other.entity(e3)); + registry.erase(entity); - other.assign(e3, 3); - other.assign(e3, '3'); + ASSERT_FALSE((registry.any_of(entity))); - ASSERT_EQ((registry.group().size()), entt::registry::size_type{1}); - ASSERT_EQ((other.group().size()), entt::registry::size_type{2}); + registry.on_construct().disconnect(); + registry.on_destroy().disconnect(); + registry.emplace(entity); - other = registry.clone(); + ASSERT_TRUE((registry.any_of(entity))); + ASSERT_FALSE(registry.all_of(entity)); +} - ASSERT_EQ(other.size(), registry.size()); - ASSERT_EQ(other.alive(), registry.alive()); +TEST(Registry, StableEmplace) { + entt::registry registry; + registry.on_construct().connect<&listener::sort>(); + registry.emplace(registry.create(), 0); - ASSERT_TRUE(other.valid(e0)); - ASSERT_FALSE(other.valid(e1)); - ASSERT_TRUE(other.valid(e2)); - ASSERT_FALSE(other.valid(e3)); + ASSERT_EQ(registry.emplace(registry.create(), 1), 1); +} - ASSERT_TRUE((other.has(e0))); - ASSERT_TRUE((other.has(e2))); +TEST(Registry, AssignEntities) { + using traits_type = entt::entt_traits; - ASSERT_EQ(other.get(e0), 0); - ASSERT_EQ(other.get(e0), 0.); - ASSERT_EQ(other.get(e2), 2); - ASSERT_EQ(other.get(e2), '2'); + entt::registry registry; + entt::entity entities[3]; + registry.create(std::begin(entities), std::end(entities)); + registry.release(entities[1]); + registry.release(entities[2]); - other = other.clone(); + entt::registry other; + const auto *data = registry.data(); + other.assign(data, data + registry.size(), registry.released()); + + ASSERT_EQ(registry.size(), other.size()); + ASSERT_TRUE(other.valid(entities[0])); + ASSERT_FALSE(other.valid(entities[1])); + ASSERT_FALSE(other.valid(entities[2])); + ASSERT_EQ(registry.create(), other.create()); + ASSERT_EQ(traits_type::to_entity(other.create()), traits_type::to_integral(entities[1])); +} - ASSERT_EQ(other.size(), registry.size()); - ASSERT_EQ(other.alive(), registry.alive()); +TEST(Registry, ScramblingPoolsIsAllowed) { + entt::registry registry; + registry.on_destroy().connect<&listener::sort>(); - ASSERT_TRUE(other.valid(e0)); - ASSERT_FALSE(other.valid(e1)); - ASSERT_TRUE(other.valid(e2)); - ASSERT_FALSE(other.valid(e3)); + for(std::size_t i{}; i < 2u; ++i) { + const auto entity = registry.create(); + registry.emplace(entity, static_cast(i)); + } - ASSERT_FALSE((other.has(e0))); - ASSERT_FALSE((other.has(e0))); - ASSERT_FALSE((other.has(e2))); - ASSERT_TRUE((other.has(e2))); + registry.destroy(registry.view().back()); - ASSERT_TRUE(other.orphan(e0)); - ASSERT_EQ(other.get(e2), '2'); + // thanks to @andranik3949 for pointing out this missing test + registry.view().each([](const auto entity, const auto &value) { + ASSERT_EQ(entt::to_integral(entity), value); + }); } -TEST(Registry, CloneMoveOnlyComponent) { +TEST(Registry, RuntimePools) { + using namespace entt::literals; + entt::registry registry; + auto &storage = registry.storage("other"_hs); const auto entity = registry.create(); - registry.assign>(entity); - registry.assign(entity); + static_assert(std::is_same_v()), typename entt::storage_traits::storage_type &>); + static_assert(std::is_same_v()), const typename entt::storage_traits::storage_type &>); + static_assert(std::is_same_v()), const typename entt::storage_traits::storage_type &>); + static_assert(std::is_same_v()), const typename entt::storage_traits::storage_type &>); + + static_assert(std::is_same_vsecond), typename entt::storage_traits::storage_type::base_type &>); + static_assert(std::is_same_vsecond), const typename entt::storage_traits::storage_type::base_type &>); + + ASSERT_NE(registry.storage("other"_hs), registry.storage().end()); + ASSERT_EQ(std::as_const(registry).storage("rehto"_hs), registry.storage().end()); + + ASSERT_EQ(®istry.storage("other"_hs), &storage); + ASSERT_NE(&std::as_const(registry).storage(), &storage); + + ASSERT_FALSE(registry.any_of(entity)); + ASSERT_FALSE(storage.contains(entity)); + + registry.emplace(entity); + + ASSERT_FALSE(storage.contains(entity)); + ASSERT_TRUE(registry.any_of(entity)); + ASSERT_EQ((entt::basic_view{registry.storage(), storage}.size_hint()), 0u); + + storage.emplace(entity); + + ASSERT_TRUE(storage.contains(entity)); + ASSERT_TRUE(registry.any_of(entity)); + ASSERT_EQ((entt::basic_view{registry.storage(), storage}.size_hint()), 1u); + + registry.destroy(entity); + + ASSERT_EQ(registry.create(entity), entity); - auto other = registry.clone(); + ASSERT_FALSE(storage.contains(entity)); + ASSERT_FALSE(registry.any_of(entity)); +} + +TEST(RegistryDeathTest, RuntimePools) { + using namespace entt::literals; + + entt::registry registry; + registry.storage("other"_hs); - ASSERT_TRUE(other.valid(entity)); - ASSERT_TRUE(other.has(entity)); - ASSERT_FALSE(other.has>(entity)); + ASSERT_DEATH(registry.storage("other"_hs), ""); + ASSERT_DEATH(std::as_const(registry).storage("other"_hs), ""); } -TEST(Registry, GetOrAssign) { +TEST(Registry, StorageProxy) { + using namespace entt::literals; + entt::registry registry; const auto entity = registry.create(); - const auto value = registry.get_or_assign(entity, 3); - ASSERT_TRUE(registry.has(entity)); - ASSERT_EQ(registry.get(entity), value); - ASSERT_EQ(registry.get(entity), 3); + auto &storage = registry.storage("int"_hs); + storage.emplace(entity); + + for(auto [id, pool]: registry.storage()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + ASSERT_TRUE(pool.contains(entity)); + ASSERT_EQ(std::addressof(storage), std::addressof(pool)); + ASSERT_EQ(id, "int"_hs); + } + + for(auto &&curr: std::as_const(registry).storage()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + ASSERT_TRUE(curr.second.contains(entity)); + ASSERT_EQ(std::addressof(storage), std::addressof(curr.second)); + ASSERT_EQ(curr.first, "int"_hs); + } } -TEST(Registry, Constness) { +TEST(Registry, StorageProxyIterator) { entt::registry registry; + const auto entity = registry.create(); + registry.emplace(entity); + + auto test = [entity](auto iterable) { + auto end{iterable.begin()}; + decltype(end) begin{}; + begin = iterable.end(); + std::swap(begin, end); - ASSERT_TRUE((std::is_same_v({})), int &>)); - ASSERT_TRUE((std::is_same_v({})), std::tuple>)); + ASSERT_EQ(begin, iterable.cbegin()); + ASSERT_EQ(end, iterable.cend()); + ASSERT_NE(begin, end); - ASSERT_TRUE((std::is_same_v({})), int *>)); - ASSERT_TRUE((std::is_same_v({})), std::tuple>)); + ASSERT_EQ(begin++, iterable.begin()); + ASSERT_EQ(begin--, iterable.end()); - ASSERT_TRUE((std::is_same_v({})), const int &>)); - ASSERT_TRUE((std::is_same_v({})), std::tuple>)); + ASSERT_EQ(begin + 1, iterable.end()); + ASSERT_EQ(end - 1, iterable.begin()); - ASSERT_TRUE((std::is_same_v({})), const int *>)); - ASSERT_TRUE((std::is_same_v({})), std::tuple>)); + ASSERT_EQ(++begin, iterable.end()); + ASSERT_EQ(--begin, iterable.begin()); + + ASSERT_EQ(begin += 1, iterable.end()); + ASSERT_EQ(begin -= 1, iterable.begin()); + + ASSERT_EQ(begin + (end - begin), iterable.end()); + ASSERT_EQ(begin - (begin - end), iterable.end()); + + ASSERT_EQ(end - (end - begin), iterable.begin()); + ASSERT_EQ(end + (begin - end), iterable.begin()); + + ASSERT_EQ(begin[0u].first, iterable.begin()->first); + ASSERT_EQ(std::addressof(begin[0u].second), std::addressof((*iterable.begin()).second)); + + ASSERT_LT(begin, end); + ASSERT_LE(begin, iterable.begin()); + + ASSERT_GT(end, begin); + ASSERT_GE(end, iterable.end()); + + ASSERT_EQ(begin[0u].first, entt::type_id().hash()); + ASSERT_TRUE(begin[0u].second.contains(entity)); + }; + + test(registry.storage()); + test(std::as_const(registry).storage()); + + decltype(std::as_const(registry).storage().begin()) cit = registry.storage().begin(); + + ASSERT_EQ(cit, registry.storage().begin()); + ASSERT_NE(cit, std::as_const(registry).storage().end()); } -TEST(Registry, BatchCreateAmbiguousCall) { - struct ambiguous { std::uint32_t foo; std::uint64_t bar; }; +TEST(Registry, StorageProxyIteratorConversion) { entt::registry registry; const auto entity = registry.create(); - std::uint32_t foo = 32u; - std::uint64_t bar = 64u; - // this should work, no other tests required - registry.assign(entity, foo, bar); + registry.emplace(entity); + + auto proxy = registry.storage(); + auto cproxy = std::as_const(registry).storage(); + + typename decltype(proxy)::iterator it = proxy.begin(); + typename decltype(cproxy)::iterator cit = it; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + + ASSERT_EQ(it->first, entt::type_id().hash()); + ASSERT_EQ((*it).second.type(), entt::type_id()); + ASSERT_EQ(it->first, cit->first); + ASSERT_EQ((*it).second.type(), (*cit).second.type()); + + ASSERT_EQ(it - cit, 0); + ASSERT_EQ(cit - it, 0); + ASSERT_LE(it, cit); + ASSERT_LE(cit, it); + ASSERT_GE(it, cit); + ASSERT_GE(cit, it); + ASSERT_EQ(it, cit); + ASSERT_NE(++cit, it); } -TEST(Registry, MoveOnlyComponent) { - // the purpose is to ensure that move only components are always accepted +TEST(Registry, NoEtoType) { entt::registry registry; const auto entity = registry.create(); - registry.assign>(entity); + + registry.emplace(entity); + registry.emplace(entity, 42); + + ASSERT_NE(registry.storage().raw(), nullptr); + ASSERT_NE(registry.try_get(entity), nullptr); + ASSERT_EQ(registry.view().get(entity), std::as_const(registry).view().get(entity)); + + auto view = registry.view(); + auto cview = std::as_const(registry).view(); + + ASSERT_EQ((std::get<0>(view.get(entity))), (std::get<0>(cview.get(entity)))); } diff --git a/modules/entt/test/entt/entity/runtime_view.cpp b/modules/entt/test/entt/entity/runtime_view.cpp index 51a544a..7ba87ad 100644 --- a/modules/entt/test/entt/entity/runtime_view.cpp +++ b/modules/entt/test/entt/entity/runtime_view.cpp @@ -1,41 +1,55 @@ -#include +#include +#include +#include #include +#include #include #include +struct stable_type { + static constexpr auto in_place_delete = true; + int value; +}; + TEST(RuntimeView, Functionalities) { entt::registry registry; - using component_type = typename decltype(registry)::component_type; + entt::runtime_view view{}; + + const auto e0 = registry.create(); + const auto e1 = registry.create(); + + ASSERT_EQ(view.size_hint(), 0u); + ASSERT_EQ(view.begin(), view.end()); + ASSERT_FALSE(view.contains(e0)); + ASSERT_FALSE(view.contains(e1)); // forces the creation of the pools - registry.reserve(0); - registry.reserve(0); + static_cast(registry.storage()); + static_cast(registry.storage()); - component_type types[] = { registry.type(), registry.type() }; - auto view = registry.runtime_view(std::begin(types), std::end(types)); + view.iterate(registry.storage()).iterate(registry.storage()); - ASSERT_TRUE(view.empty()); + ASSERT_EQ(view.size_hint(), 0u); - const auto e0 = registry.create(); - registry.assign(e0); + registry.emplace(e0); + registry.emplace(e1); - const auto e1 = registry.create(); - registry.assign(e1); + ASSERT_NE(view.size_hint(), 0u); - ASSERT_FALSE(view.empty()); + registry.emplace(e1); - registry.assign(e1); + ASSERT_EQ(view.size_hint(), 1u); - auto it = registry.runtime_view(std::begin(types), std::end(types)).begin(); + auto it = view.begin(); ASSERT_EQ(*it, e1); - ASSERT_EQ(++it, (registry.runtime_view(std::begin(types), std::end(types)).end())); + ASSERT_EQ(++it, (view.end())); - ASSERT_NO_THROW((registry.runtime_view(std::begin(types), std::end(types)).begin()++)); - ASSERT_NO_THROW((++registry.runtime_view(std::begin(types), std::end(types)).begin())); + ASSERT_NO_FATAL_FAILURE((view.begin()++)); + ASSERT_NO_FATAL_FAILURE((++view.begin())); ASSERT_NE(view.begin(), view.end()); - ASSERT_EQ(view.size(), decltype(view.size()){1}); + ASSERT_EQ(view.size_hint(), 1u); registry.get(e0) = '1'; registry.get(e1) = '2'; @@ -45,22 +59,26 @@ TEST(RuntimeView, Functionalities) { ASSERT_EQ(registry.get(entity), 42); ASSERT_EQ(registry.get(entity), '2'); } + + entt::runtime_view empty{}; + + ASSERT_EQ(empty.size_hint(), 0u); + ASSERT_EQ(empty.begin(), empty.end()); } TEST(RuntimeView, Iterator) { entt::registry registry; - using component_type = typename decltype(registry)::component_type; + entt::runtime_view view{}; const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); + registry.emplace(entity); + registry.emplace(entity); - component_type types[] = { registry.type(), registry.type() }; - auto view = registry.runtime_view(std::begin(types), std::end(types)); - using iterator_type = typename decltype(view)::iterator_type; + view.iterate(registry.storage()).iterate(registry.storage()); + using iterator = typename decltype(view)::iterator; - iterator_type end{view.begin()}; - iterator_type begin{}; + iterator end{view.begin()}; + iterator begin{}; begin = view.end(); std::swap(begin, end); @@ -68,26 +86,31 @@ TEST(RuntimeView, Iterator) { ASSERT_EQ(end, view.end()); ASSERT_NE(begin, end); - ASSERT_EQ(view.begin()++, view.begin()); - ASSERT_EQ(++view.begin(), view.end()); + ASSERT_EQ(begin++, view.begin()); + ASSERT_EQ(begin--, view.end()); + + ASSERT_EQ(++begin, view.end()); + ASSERT_EQ(--begin, view.begin()); + + ASSERT_EQ(*begin, entity); + ASSERT_EQ(*begin.operator->(), entity); } TEST(RuntimeView, Contains) { entt::registry registry; - using component_type = typename decltype(registry)::component_type; + entt::runtime_view view{}; const auto e0 = registry.create(); - registry.assign(e0); - registry.assign(e0); + registry.emplace(e0); + registry.emplace(e0); const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(e1); + registry.emplace(e1); registry.destroy(e0); - component_type types[] = { registry.type(), registry.type() }; - auto view = registry.runtime_view(std::begin(types), std::end(types)); + view.iterate(registry.storage()).iterate(registry.storage()); ASSERT_FALSE(view.contains(e0)); ASSERT_TRUE(view.contains(e1)); @@ -95,114 +118,162 @@ TEST(RuntimeView, Contains) { TEST(RuntimeView, Empty) { entt::registry registry; - using component_type = typename decltype(registry)::component_type; + entt::runtime_view view{}; const auto e0 = registry.create(); - registry.assign(e0); - registry.assign(e0); - registry.assign(e0); + registry.emplace(e0); + registry.emplace(e0); + registry.emplace(e0); const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(e1); + registry.emplace(e1); - component_type types[] = { registry.type(), registry.type(), registry.type() }; - auto view = registry.runtime_view(std::begin(types), std::end(types)); + view.iterate(registry.storage()) + .iterate(registry.storage()) + .iterate(registry.storage()); - for(auto entity: view) { - (void)entity; - FAIL(); - } + ASSERT_FALSE(view.contains(e0)); + ASSERT_FALSE(view.contains(e1)); + ASSERT_EQ(view.begin(), view.end()); + ASSERT_EQ((std::find(view.begin(), view.end(), e0)), view.end()); + ASSERT_EQ((std::find(view.begin(), view.end(), e1)), view.end()); } TEST(RuntimeView, Each) { entt::registry registry; - using component_type = typename decltype(registry)::component_type; + entt::runtime_view view{}; const auto e0 = registry.create(); - registry.assign(e0); - registry.assign(e0); + registry.emplace(e0); + registry.emplace(e0); const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(e1); - component_type types[] = { registry.type(), registry.type() }; - auto view = registry.runtime_view(std::begin(types), std::end(types)); - std::size_t cnt = 0; + view.iterate(registry.storage()).iterate(registry.storage()); - view.each([&cnt](auto) { ++cnt; }); - - ASSERT_EQ(cnt, std::size_t{2}); + view.each([e0](const auto entt) { + ASSERT_EQ(entt, e0); + }); } TEST(RuntimeView, EachWithHoles) { entt::registry registry; - using component_type = typename decltype(registry)::component_type; + entt::runtime_view view{}; const auto e0 = registry.create(); const auto e1 = registry.create(); const auto e2 = registry.create(); - registry.assign(e0, '0'); - registry.assign(e1, '1'); + registry.emplace(e0, '0'); + registry.emplace(e1, '1'); - registry.assign(e0, 0); - registry.assign(e2, 2); + registry.emplace(e0, 0); + registry.emplace(e2, 2); - component_type types[] = { registry.type(), registry.type() }; - auto view = registry.runtime_view(std::begin(types), std::end(types)); + view.iterate(registry.storage()).iterate(registry.storage()); view.each([e0](auto entity) { ASSERT_EQ(e0, entity); }); } -TEST(RuntimeView, MissingPool) { +TEST(RuntimeView, ExcludedComponents) { entt::registry registry; - using component_type = typename decltype(registry)::component_type; + entt::runtime_view view{}; const auto e0 = registry.create(); - registry.assign(e0); + registry.emplace(e0); - component_type types[] = { registry.type(), registry.type() }; - auto view = registry.runtime_view(std::begin(types), std::end(types)); + const auto e1 = registry.create(); + registry.emplace(e1); + registry.emplace(e1); - ASSERT_TRUE(view.empty()); - ASSERT_EQ(view.size(), decltype(view.size()){0}); + view.iterate(registry.storage()) + .exclude(registry.storage()) + .exclude(registry.storage()); - registry.assign(e0); + ASSERT_TRUE(view.contains(e0)); + ASSERT_FALSE(view.contains(e1)); - ASSERT_TRUE(view.empty()); - ASSERT_EQ(view.size(), decltype(view.size()){0}); - ASSERT_FALSE(view.contains(e0)); + view.each([e0](auto entity) { + ASSERT_EQ(e0, entity); + }); +} - view.each([](auto) { FAIL(); }); +TEST(RuntimeView, StableType) { + entt::registry registry; + entt::runtime_view view{}; - for(auto entity: view) { - (void)entity; - FAIL(); + const auto e0 = registry.create(); + const auto e1 = registry.create(); + const auto e2 = registry.create(); + + registry.emplace(e0); + registry.emplace(e1); + registry.emplace(e2); + + registry.emplace(e0); + registry.emplace(e1); + + registry.remove(e1); + + view.iterate(registry.storage()).iterate(registry.storage()); + + ASSERT_EQ(view.size_hint(), 2u); + ASSERT_TRUE(view.contains(e0)); + ASSERT_FALSE(view.contains(e1)); + + ASSERT_EQ(*view.begin(), e0); + ASSERT_EQ(++view.begin(), view.end()); + + view.each([e0](const auto entt) { + ASSERT_EQ(e0, entt); + }); + + for(auto entt: view) { + static_assert(std::is_same_v); + ASSERT_EQ(e0, entt); } + + registry.compact(); + + ASSERT_EQ(view.size_hint(), 1u); } -TEST(RuntimeView, EmptyRange) { +TEST(RuntimeView, StableTypeWithExcludedComponent) { entt::registry registry; - using component_type = typename decltype(registry)::component_type; + entt::runtime_view view{}; - const auto e0 = registry.create(); - registry.assign(e0); + const auto entity = registry.create(); + const auto other = registry.create(); - const component_type *ptr = nullptr; - auto view = registry.runtime_view(ptr, ptr); + registry.emplace(entity, 0); + registry.emplace(other, 42); + registry.emplace(entity); - ASSERT_TRUE(view.empty()); - ASSERT_EQ(view.size(), decltype(view.size()){0}); - ASSERT_FALSE(view.contains(e0)); + view.iterate(registry.storage()).exclude(registry.storage()); - view.each([](auto) { FAIL(); }); + ASSERT_EQ(view.size_hint(), 2u); + ASSERT_FALSE(view.contains(entity)); + ASSERT_TRUE(view.contains(other)); - for(auto entity: view) { - (void)entity; - FAIL(); + registry.destroy(entity); + + ASSERT_EQ(view.size_hint(), 2u); + ASSERT_FALSE(view.contains(entity)); + ASSERT_TRUE(view.contains(other)); + + for(auto entt: view) { + constexpr entt::entity tombstone = entt::tombstone; + ASSERT_NE(entt, tombstone); + ASSERT_EQ(entt, other); } + + view.each([other](const auto entt) { + constexpr entt::entity tombstone = entt::tombstone; + ASSERT_NE(entt, tombstone); + ASSERT_EQ(entt, other); + }); } diff --git a/modules/entt/test/entt/entity/sigh_storage_mixin.cpp b/modules/entt/test/entt/entity/sigh_storage_mixin.cpp new file mode 100644 index 0000000..03b7241 --- /dev/null +++ b/modules/entt/test/entt/entity/sigh_storage_mixin.cpp @@ -0,0 +1,283 @@ +#include +#include +#include +#include + +struct empty_type {}; + +struct stable_type { + static constexpr auto in_place_delete = true; + int value{}; +}; + +struct non_default_constructible { + non_default_constructible() = delete; + + non_default_constructible(int v) + : value{v} {} + + int value{}; +}; + +struct counter { + int value{}; +}; + +void listener(counter &counter, entt::registry &, entt::entity) { + ++counter.value; +} + +TEST(SighStorageMixin, GenericType) { + entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; + entt::sigh_storage_mixin> pool; + entt::sparse_set &base = pool; + entt::registry registry{}; + + pool.bind(entt::forward_as_any(registry)); + + counter on_construct{}; + counter on_destroy{}; + + pool.on_construct().connect<&listener>(on_construct); + pool.on_destroy().connect<&listener>(on_destroy); + + ASSERT_NE(base.emplace(entities[0u]), base.end()); + + pool.emplace(entities[1u]); + + ASSERT_EQ(on_construct.value, 2); + ASSERT_EQ(on_destroy.value, 0); + ASSERT_FALSE(pool.empty()); + + ASSERT_EQ(pool.get(entities[0u]), 0); + ASSERT_EQ(pool.get(entities[1u]), 0); + + base.erase(entities[0u]); + pool.erase(entities[1u]); + + ASSERT_EQ(on_construct.value, 2); + ASSERT_EQ(on_destroy.value, 2); + ASSERT_TRUE(pool.empty()); + + ASSERT_NE(base.insert(std::begin(entities), std::end(entities)), base.end()); + + ASSERT_EQ(pool.get(entities[0u]), 0); + ASSERT_EQ(pool.get(entities[1u]), 0); + ASSERT_FALSE(pool.empty()); + + base.erase(entities[1u]); + + ASSERT_EQ(on_construct.value, 4); + ASSERT_EQ(on_destroy.value, 3); + ASSERT_FALSE(pool.empty()); + + base.erase(entities[0u]); + + ASSERT_EQ(on_construct.value, 4); + ASSERT_EQ(on_destroy.value, 4); + ASSERT_TRUE(pool.empty()); + + pool.insert(std::begin(entities), std::end(entities), 3); + + ASSERT_EQ(on_construct.value, 6); + ASSERT_EQ(on_destroy.value, 4); + ASSERT_FALSE(pool.empty()); + + ASSERT_EQ(pool.get(entities[0u]), 3); + ASSERT_EQ(pool.get(entities[1u]), 3); + + pool.erase(std::begin(entities), std::end(entities)); + + ASSERT_EQ(on_construct.value, 6); + ASSERT_EQ(on_destroy.value, 6); + ASSERT_TRUE(pool.empty()); +} + +TEST(SighStorageMixin, StableType) { + entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; + entt::sigh_storage_mixin> pool; + entt::sparse_set &base = pool; + entt::registry registry{}; + + pool.bind(entt::forward_as_any(registry)); + + counter on_construct{}; + counter on_destroy{}; + + pool.on_construct().connect<&listener>(on_construct); + pool.on_destroy().connect<&listener>(on_destroy); + + ASSERT_NE(base.emplace(entities[0u]), base.end()); + + pool.emplace(entities[1u]); + + ASSERT_EQ(on_construct.value, 2); + ASSERT_EQ(on_destroy.value, 0); + ASSERT_FALSE(pool.empty()); + + ASSERT_EQ(pool.get(entities[0u]).value, 0); + ASSERT_EQ(pool.get(entities[1u]).value, 0); + + base.erase(entities[0u]); + pool.erase(entities[1u]); + + ASSERT_EQ(on_construct.value, 2); + ASSERT_EQ(on_destroy.value, 2); + ASSERT_FALSE(pool.empty()); + + ASSERT_NE(base.insert(std::begin(entities), std::end(entities)), base.end()); + + ASSERT_EQ(pool.get(entities[0u]).value, 0); + ASSERT_EQ(pool.get(entities[1u]).value, 0); + ASSERT_FALSE(pool.empty()); + + base.erase(entities[1u]); + + ASSERT_EQ(on_construct.value, 4); + ASSERT_EQ(on_destroy.value, 3); + ASSERT_FALSE(pool.empty()); + + base.erase(entities[0u]); + + ASSERT_EQ(on_construct.value, 4); + ASSERT_EQ(on_destroy.value, 4); + ASSERT_FALSE(pool.empty()); + + pool.insert(std::begin(entities), std::end(entities), stable_type{3}); + + ASSERT_EQ(on_construct.value, 6); + ASSERT_EQ(on_destroy.value, 4); + ASSERT_FALSE(pool.empty()); + + ASSERT_EQ(pool.get(entities[0u]).value, 3); + ASSERT_EQ(pool.get(entities[1u]).value, 3); + + pool.erase(std::begin(entities), std::end(entities)); + + ASSERT_EQ(on_construct.value, 6); + ASSERT_EQ(on_destroy.value, 6); + ASSERT_FALSE(pool.empty()); +} + +TEST(SighStorageMixin, EmptyType) { + entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; + entt::sigh_storage_mixin> pool; + entt::sparse_set &base = pool; + entt::registry registry{}; + + pool.bind(entt::forward_as_any(registry)); + + counter on_construct{}; + counter on_destroy{}; + + pool.on_construct().connect<&listener>(on_construct); + pool.on_destroy().connect<&listener>(on_destroy); + + ASSERT_NE(base.emplace(entities[0u]), base.end()); + + pool.emplace(entities[1u]); + + ASSERT_EQ(on_construct.value, 2); + ASSERT_EQ(on_destroy.value, 0); + ASSERT_FALSE(pool.empty()); + + ASSERT_TRUE(pool.contains(entities[0u])); + ASSERT_TRUE(pool.contains(entities[1u])); + + base.erase(entities[0u]); + pool.erase(entities[1u]); + + ASSERT_EQ(on_construct.value, 2); + ASSERT_EQ(on_destroy.value, 2); + ASSERT_TRUE(pool.empty()); + + ASSERT_NE(base.insert(std::begin(entities), std::end(entities)), base.end()); + + ASSERT_TRUE(pool.contains(entities[0u])); + ASSERT_TRUE(pool.contains(entities[1u])); + ASSERT_FALSE(pool.empty()); + + base.erase(entities[1u]); + + ASSERT_EQ(on_construct.value, 4); + ASSERT_EQ(on_destroy.value, 3); + ASSERT_FALSE(pool.empty()); + + base.erase(entities[0u]); + + ASSERT_EQ(on_construct.value, 4); + ASSERT_EQ(on_destroy.value, 4); + ASSERT_TRUE(pool.empty()); + + pool.insert(std::begin(entities), std::end(entities)); + + ASSERT_EQ(on_construct.value, 6); + ASSERT_EQ(on_destroy.value, 4); + ASSERT_FALSE(pool.empty()); + + ASSERT_TRUE(pool.contains(entities[0u])); + ASSERT_TRUE(pool.contains(entities[1u])); + + pool.erase(std::begin(entities), std::end(entities)); + + ASSERT_EQ(on_construct.value, 6); + ASSERT_EQ(on_destroy.value, 6); + ASSERT_TRUE(pool.empty()); +} + +TEST(SighStorageMixin, NonDefaultConstructibleType) { + entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; + entt::sigh_storage_mixin> pool; + entt::sparse_set &base = pool; + entt::registry registry{}; + + pool.bind(entt::forward_as_any(registry)); + + counter on_construct{}; + counter on_destroy{}; + + pool.on_construct().connect<&listener>(on_construct); + pool.on_destroy().connect<&listener>(on_destroy); + + ASSERT_EQ(base.emplace(entities[0u]), base.end()); + + pool.emplace(entities[1u], 3); + + ASSERT_EQ(pool.size(), 1u); + ASSERT_EQ(on_construct.value, 2); + ASSERT_EQ(on_destroy.value, 0); + ASSERT_FALSE(pool.empty()); + + ASSERT_FALSE(pool.contains(entities[0u])); + ASSERT_EQ(pool.get(entities[1u]).value, 3); + + base.erase(entities[1u]); + + ASSERT_EQ(pool.size(), 0u); + ASSERT_EQ(on_construct.value, 2); + ASSERT_EQ(on_destroy.value, 1); + ASSERT_TRUE(pool.empty()); + + ASSERT_EQ(base.insert(std::begin(entities), std::end(entities)), base.end()); + + ASSERT_FALSE(pool.contains(entities[0u])); + ASSERT_FALSE(pool.contains(entities[1u])); + ASSERT_TRUE(pool.empty()); + + pool.insert(std::begin(entities), std::end(entities), 3); + + ASSERT_EQ(pool.size(), 2u); + ASSERT_EQ(on_construct.value, 6); + ASSERT_EQ(on_destroy.value, 1); + ASSERT_FALSE(pool.empty()); + + ASSERT_EQ(pool.get(entities[0u]).value, 3); + ASSERT_EQ(pool.get(entities[1u]).value, 3); + + pool.erase(std::begin(entities), std::end(entities)); + + ASSERT_EQ(pool.size(), 0u); + ASSERT_EQ(on_construct.value, 6); + ASSERT_EQ(on_destroy.value, 3); + ASSERT_TRUE(pool.empty()); +} diff --git a/modules/entt/test/entt/entity/snapshot.cpp b/modules/entt/test/entt/entity/snapshot.cpp index 3b999cb..3a6f1c8 100644 --- a/modules/entt/test/entt/entity/snapshot.cpp +++ b/modules/entt/test/entt/entity/snapshot.cpp @@ -1,21 +1,44 @@ -#include +#include +#include #include +#include +#include #include #include -#include #include +#include +#include + +struct noncopyable_component { + noncopyable_component() + : value{} {} + + explicit noncopyable_component(int v) + : value{v} {} + + noncopyable_component(const noncopyable_component &) = delete; + noncopyable_component(noncopyable_component &&) = default; + + noncopyable_component &operator=(const noncopyable_component &) = delete; + noncopyable_component &operator=(noncopyable_component &&) = default; + + int value; +}; template struct output_archive { output_archive(Storage &instance) - : storage{instance} - {} + : storage{instance} {} template - void operator()(const Value &... value) { + void operator()(const Value &...value) { (std::get>(storage).push(value), ...); } + void operator()(const entt::entity &entity, const noncopyable_component &instance) { + (*this)(entity, instance.value); + } + private: Storage &storage; }; @@ -23,11 +46,10 @@ private: template struct input_archive { input_archive(Storage &instance) - : storage{instance} - {} + : storage{instance} {} template - void operator()(Value &... value) { + void operator()(Value &...value) { auto assign = [this](auto &val) { auto &queue = std::get>>(storage); val = queue.front(); @@ -37,6 +59,10 @@ struct input_archive { (assign(value), ...); } + void operator()(entt::entity &entity, noncopyable_component &instance) { + (*this)(entity, instance.value); + } + private: Storage &storage; }; @@ -53,56 +79,56 @@ struct what_a_component { std::vector quux; }; +struct map_component { + std::map keys; + std::map values; + std::map both; +}; + TEST(Snapshot, Dump) { + using traits_type = entt::entt_traits; + entt::registry registry; const auto e0 = registry.create(); - registry.assign(e0, 42); - registry.assign(e0, 'c'); - registry.assign(e0, .1); + registry.emplace(e0, 42); + registry.emplace(e0, 'c'); + registry.emplace(e0, .1); const auto e1 = registry.create(); const auto e2 = registry.create(); - registry.assign(e2, 3); + registry.emplace(e2, 3); const auto e3 = registry.create(); - registry.assign(e3); - registry.assign(e3, '0'); + registry.emplace(e3); + registry.emplace(e3, '0'); registry.destroy(e1); auto v1 = registry.current(e1); using storage_type = std::tuple< + std::queue, std::queue, std::queue, std::queue, std::queue, std::queue, - std::queue - >; + std::queue>; storage_type storage; output_archive output{storage}; input_archive input{storage}; - registry.snapshot() - .entities(output) - .destroyed(output) - .component(output); - - registry.reset(); + entt::snapshot{registry}.entities(output).component(output); + registry.clear(); ASSERT_FALSE(registry.valid(e0)); ASSERT_FALSE(registry.valid(e1)); ASSERT_FALSE(registry.valid(e2)); ASSERT_FALSE(registry.valid(e3)); - registry.loader() - .entities(input) - .destroyed(input) - .component(input) - .orphans(); + entt::snapshot_loader{registry}.entities(input).component(input).orphans(); ASSERT_TRUE(registry.valid(e0)); ASSERT_FALSE(registry.valid(e1)); @@ -119,57 +145,52 @@ TEST(Snapshot, Dump) { ASSERT_EQ(registry.current(e1), v1); ASSERT_EQ(registry.get(e2), 3); ASSERT_EQ(registry.get(e3), '0'); - ASSERT_TRUE(registry.has(e3)); + ASSERT_TRUE(registry.all_of(e3)); - ASSERT_TRUE(registry.empty()); + ASSERT_TRUE(registry.storage().empty()); } TEST(Snapshot, Partial) { + using traits_type = entt::entt_traits; + entt::registry registry; const auto e0 = registry.create(); - registry.assign(e0, 42); - registry.assign(e0, 'c'); - registry.assign(e0, .1); + registry.emplace(e0, 42); + registry.emplace(e0, 'c'); + registry.emplace(e0, .1); const auto e1 = registry.create(); const auto e2 = registry.create(); - registry.assign(e2, 3); + registry.emplace(e2, 3); const auto e3 = registry.create(); - registry.assign(e3, '0'); + registry.emplace(e3, '0'); registry.destroy(e1); auto v1 = registry.current(e1); using storage_type = std::tuple< + std::queue, std::queue, std::queue, std::queue, - std::queue - >; + std::queue>; storage_type storage; output_archive output{storage}; input_archive input{storage}; - registry.snapshot() - .entities(output) - .destroyed(output) - .component(output); - - registry.reset(); + entt::snapshot{registry}.entities(output).component(output); + registry.clear(); ASSERT_FALSE(registry.valid(e0)); ASSERT_FALSE(registry.valid(e1)); ASSERT_FALSE(registry.valid(e2)); ASSERT_FALSE(registry.valid(e3)); - registry.loader() - .entities(input) - .destroyed(input) - .component(input); + entt::snapshot_loader{registry}.entities(input).component(input); ASSERT_TRUE(registry.valid(e0)); ASSERT_FALSE(registry.valid(e1)); @@ -178,26 +199,20 @@ TEST(Snapshot, Partial) { ASSERT_EQ(registry.get(e0), 42); ASSERT_EQ(registry.get(e0), 'c'); - ASSERT_FALSE(registry.has(e0)); + ASSERT_FALSE(registry.all_of(e0)); ASSERT_EQ(registry.current(e1), v1); ASSERT_EQ(registry.get(e2), 3); ASSERT_EQ(registry.get(e3), '0'); - registry.snapshot() - .entities(output) - .destroyed(output); - - registry.reset(); + entt::snapshot{registry}.entities(output); + registry.clear(); ASSERT_FALSE(registry.valid(e0)); ASSERT_FALSE(registry.valid(e1)); ASSERT_FALSE(registry.valid(e2)); ASSERT_FALSE(registry.valid(e3)); - registry.loader() - .entities(input) - .destroyed(input) - .orphans(); + entt::snapshot_loader{registry}.entities(input).orphans(); ASSERT_FALSE(registry.valid(e0)); ASSERT_FALSE(registry.valid(e1)); @@ -206,21 +221,25 @@ TEST(Snapshot, Partial) { } TEST(Snapshot, Iterator) { + using traits_type = entt::entt_traits; + entt::registry registry; for(auto i = 0; i < 50; ++i) { const auto entity = registry.create(); - registry.assign(entity, i, i); + registry.emplace(entity, i, i); + registry.emplace(entity, i); if(i % 2) { - registry.assign(entity); + registry.emplace(entity); } } using storage_type = std::tuple< + std::queue, std::queue, - std::queue - >; + std::queue, + std::queue>; storage_type storage; output_archive output{storage}; @@ -229,18 +248,20 @@ TEST(Snapshot, Iterator) { const auto view = registry.view(); const auto size = view.size(); - registry.snapshot().component(output, view.begin(), view.end()); - registry.reset(); - registry.loader().component(input); + entt::snapshot{registry}.component(output, view.begin(), view.end()); + registry.clear(); + entt::snapshot_loader{registry}.component(input); ASSERT_EQ(registry.view().size(), size); registry.view().each([](const auto entity, const auto &) { - ASSERT_TRUE(entity % 2); + ASSERT_NE(entt::to_integral(entity) % 2u, 0u); }); } TEST(Snapshot, Continuous) { + using traits_type = entt::entt_traits; + entt::registry src; entt::registry dst; @@ -250,31 +271,36 @@ TEST(Snapshot, Continuous) { entt::entity entity; using storage_type = std::tuple< + std::queue, std::queue, std::queue, std::queue, - std::queue - >; + std::queue, + std::queue, + std::queue>; storage_type storage; output_archive output{storage}; input_archive input{storage}; for(int i = 0; i < 10; ++i) { - src.create(); + static_cast(src.create()); } - src.reset(); + src.clear(); for(int i = 0; i < 5; ++i) { entity = src.create(); entities.push_back(entity); - src.assign(entity); - src.assign(entity, i, i); + src.emplace(entity); + src.emplace(entity, i, i); + src.emplace(entity, i); if(i % 2) { - src.assign(entity, entity); + src.emplace(entity, entity); + } else { + src.emplace(entity); } } @@ -282,26 +308,39 @@ TEST(Snapshot, Continuous) { what_a_component.quux.insert(what_a_component.quux.begin(), entities.begin(), entities.end()); }); + src.view().each([&entities](auto, auto &map_component) { + for(std::size_t i = 0; i < entities.size(); ++i) { + map_component.keys.insert({entities[i], int(i)}); + map_component.values.insert({int(i), entities[i]}); + map_component.both.insert({entities[entities.size() - i - 1], entities[i]}); + } + }); + entity = dst.create(); - dst.assign(entity); - dst.assign(entity, -1, -1); + dst.emplace(entity); + dst.emplace(entity, -1, -1); + dst.emplace(entity, -1); - src.snapshot() - .entities(output) - .destroyed(output) - .component(output); + entt::snapshot{src}.entities(output).component(output); loader.entities(input) - .destroyed(input) - .component(input, &what_a_component::bar, &what_a_component::quux) - .orphans(); + .component( + input, + &what_a_component::bar, + &what_a_component::quux, + &map_component::keys, + &map_component::values, + &map_component::both) + .orphans(); decltype(dst.size()) a_component_cnt{}; decltype(dst.size()) another_component_cnt{}; decltype(dst.size()) what_a_component_cnt{}; + decltype(dst.size()) map_component_cnt{}; + decltype(dst.size()) noncopyable_component_cnt{}; dst.each([&dst, &a_component_cnt](auto entt) { - ASSERT_TRUE(dst.has(entt)); + ASSERT_TRUE(dst.all_of(entt)); ++a_component_cnt; }); @@ -320,27 +359,53 @@ TEST(Snapshot, Continuous) { ++what_a_component_cnt; }); + dst.view().each([&dst, &map_component_cnt](const auto &component) { + for(auto child: component.keys) { + ASSERT_TRUE(dst.valid(child.first)); + } + + for(auto child: component.values) { + ASSERT_TRUE(dst.valid(child.second)); + } + + for(auto child: component.both) { + ASSERT_TRUE(dst.valid(child.first)); + ASSERT_TRUE(dst.valid(child.second)); + } + + ++map_component_cnt; + }); + + dst.view().each([&dst, &noncopyable_component_cnt](auto, const auto &component) { + ++noncopyable_component_cnt; + ASSERT_EQ(component.value, static_cast(dst.storage().size() - noncopyable_component_cnt - 1u)); + }); + src.view().each([](auto, auto &component) { component.value = 2 * component.key; }); auto size = dst.size(); - src.snapshot() - .entities(output) - .destroyed(output) - .component(output); + entt::snapshot{src}.entities(output).component(output); loader.entities(input) - .destroyed(input) - .component(input, &what_a_component::bar, &what_a_component::quux) - .orphans(); + .component( + input, + &what_a_component::bar, + &what_a_component::quux, + &map_component::keys, + &map_component::values, + &map_component::both) + .orphans(); ASSERT_EQ(size, dst.size()); - ASSERT_EQ(dst.size(), a_component_cnt); - ASSERT_EQ(dst.size(), another_component_cnt); - ASSERT_EQ(dst.size(), what_a_component_cnt); + ASSERT_EQ(dst.storage().size(), a_component_cnt); + ASSERT_EQ(dst.storage().size(), another_component_cnt); + ASSERT_EQ(dst.storage().size(), what_a_component_cnt); + ASSERT_EQ(dst.storage().size(), map_component_cnt); + ASSERT_EQ(dst.storage().size(), noncopyable_component_cnt); dst.view().each([](auto, auto &component) { ASSERT_EQ(component.value, component.key < 0 ? -1 : (2 * component.key)); @@ -352,15 +417,17 @@ TEST(Snapshot, Continuous) { component.bar = entity; }); - src.snapshot() - .entities(output) - .destroyed(output) - .component(output); + entt::snapshot{src}.entities(output).component(output); loader.entities(input) - .destroyed(input) - .component(input, &what_a_component::bar, &what_a_component::quux) - .orphans(); + .component( + input, + &what_a_component::bar, + &what_a_component::quux, + &map_component::keys, + &map_component::values, + &map_component::both) + .orphans(); dst.view().each([&loader, entity](auto, auto &component) { ASSERT_EQ(component.bar, loader.map(entity)); @@ -374,22 +441,24 @@ TEST(Snapshot, Continuous) { src.destroy(entity); loader.shrink(); - src.snapshot() - .entities(output) - .destroyed(output) - .component(output); + entt::snapshot{src}.entities(output).component(output); loader.entities(input) - .destroyed(input) - .component(input, &what_a_component::bar, &what_a_component::quux) - .orphans() - .shrink(); + .component( + input, + &what_a_component::bar, + &what_a_component::quux, + &map_component::keys, + &map_component::values, + &map_component::both) + .orphans() + .shrink(); dst.view().each([&dst](auto, auto &component) { ASSERT_FALSE(dst.valid(component.bar)); }); - ASSERT_FALSE(loader.has(entity)); + ASSERT_FALSE(loader.contains(entity)); entity = src.create(); @@ -397,51 +466,59 @@ TEST(Snapshot, Continuous) { component.bar = entity; }); - dst.reset(); - a_component_cnt = src.size(); + dst.clear(); + a_component_cnt = src.storage().size(); - src.snapshot() - .entities(output) - .destroyed(output) - .component(output); + entt::snapshot{src}.entities(output).component(output); loader.entities(input) - .destroyed(input) - .component(input, &what_a_component::bar, &what_a_component::quux) - .orphans(); - - ASSERT_EQ(dst.size(), a_component_cnt); - - src.reset(); + .component( + input, + &what_a_component::bar, + &what_a_component::quux, + &map_component::keys, + &map_component::values, + &map_component::both) + .orphans(); + + ASSERT_EQ(dst.storage().size(), a_component_cnt); + + src.clear(); a_component_cnt = {}; - src.snapshot() - .entities(output) - .destroyed(output) - .component(output); + entt::snapshot{src}.entities(output).component(output); loader.entities(input) - .destroyed(input) - .component(input, &what_a_component::bar, &what_a_component::quux) - .orphans(); - - ASSERT_EQ(dst.size(), a_component_cnt); + .component( + input, + &what_a_component::bar, + &what_a_component::quux, + &map_component::keys, + &map_component::values, + &map_component::both) + .orphans(); + + ASSERT_EQ(dst.storage().size(), a_component_cnt); } TEST(Snapshot, MoreOnShrink) { + using traits_type = entt::entt_traits; + entt::registry src; entt::registry dst; entt::continuous_loader loader{dst}; - using storage_type = std::tuple>; + using storage_type = std::tuple< + std::queue, + std::queue>; storage_type storage; output_archive output{storage}; input_archive input{storage}; auto entity = src.create(); - src.snapshot().entities(output); + entt::snapshot{src}.entities(output); loader.entities(input).shrink(); ASSERT_TRUE(dst.valid(entity)); @@ -452,39 +529,55 @@ TEST(Snapshot, MoreOnShrink) { } TEST(Snapshot, SyncDataMembers) { + using traits_type = entt::entt_traits; + entt::registry src; entt::registry dst; entt::continuous_loader loader{dst}; using storage_type = std::tuple< + std::queue, std::queue, - std::queue - >; + std::queue, + std::queue>; storage_type storage; output_archive output{storage}; input_archive input{storage}; - src.create(); - src.create(); + static_cast(src.create()); + static_cast(src.create()); - src.reset(); + src.clear(); auto parent = src.create(); auto child = src.create(); - src.assign(parent, entt::null); - src.assign(child, parent).quux.push_back(child); + src.emplace(parent, entt::null); + src.emplace(child, parent).quux.push_back(child); - src.snapshot().entities(output).component(output); - loader.entities(input).component(input, &what_a_component::bar, &what_a_component::quux); + src.emplace( + child, + decltype(map_component::keys){{{child, 10}}}, + decltype(map_component::values){{{10, child}}}, + decltype(map_component::both){{{child, child}}}); + + entt::snapshot{src}.entities(output).component(output); + + loader.entities(input).component( + input, + &what_a_component::bar, + &what_a_component::quux, + &map_component::keys, + &map_component::values, + &map_component::both); ASSERT_FALSE(dst.valid(parent)); ASSERT_FALSE(dst.valid(child)); - ASSERT_TRUE(dst.has(loader.map(parent))); - ASSERT_TRUE(dst.has(loader.map(child))); + ASSERT_TRUE(dst.all_of(loader.map(parent))); + ASSERT_TRUE(dst.all_of(loader.map(child))); ASSERT_EQ(dst.get(loader.map(parent)).bar, static_cast(entt::null)); @@ -492,4 +585,9 @@ TEST(Snapshot, SyncDataMembers) { ASSERT_EQ(component.bar, loader.map(parent)); ASSERT_EQ(component.quux[0], loader.map(child)); + + const auto &foobar = dst.get(loader.map(child)); + ASSERT_EQ(foobar.keys.at(loader.map(child)), 10); + ASSERT_EQ(foobar.values.at(10), loader.map(child)); + ASSERT_EQ(foobar.both.at(loader.map(child)), loader.map(child)); } diff --git a/modules/entt/test/entt/entity/sparse_set.cpp b/modules/entt/test/entt/entity/sparse_set.cpp index 6a0476e..c6d5f36 100644 --- a/modules/entt/test/entt/entity/sparse_set.cpp +++ b/modules/entt/test/entt/entity/sparse_set.cpp @@ -1,164 +1,944 @@ -#include -#include +#include +#include #include #include +#include #include +#include #include +#include "../common/throwing_allocator.hpp" struct empty_type {}; -struct boxed_int { int value; }; + +struct boxed_int { + int value; +}; TEST(SparseSet, Functionalities) { - entt::sparse_set set; + entt::sparse_set set; + + ASSERT_NO_THROW([[maybe_unused]] auto alloc = set.get_allocator()); + ASSERT_EQ(set.type(), entt::type_id()); set.reserve(42); - ASSERT_EQ(set.capacity(), 42); + ASSERT_EQ(set.capacity(), 42u); ASSERT_TRUE(set.empty()); ASSERT_EQ(set.size(), 0u); ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end()); ASSERT_EQ(set.begin(), set.end()); - ASSERT_FALSE(set.has(0)); - ASSERT_FALSE(set.has(42)); + ASSERT_FALSE(set.contains(entt::entity{0})); + ASSERT_FALSE(set.contains(entt::entity{42})); - set.construct(42); + set.reserve(0); - ASSERT_EQ(set.get(42), 0u); + ASSERT_EQ(set.capacity(), 42u); + ASSERT_TRUE(set.empty()); + + set.emplace(entt::entity{42}); ASSERT_FALSE(set.empty()); ASSERT_EQ(set.size(), 1u); ASSERT_NE(std::as_const(set).begin(), std::as_const(set).end()); ASSERT_NE(set.begin(), set.end()); - ASSERT_FALSE(set.has(0)); - ASSERT_TRUE(set.has(42)); - ASSERT_EQ(set.get(42), 0u); + ASSERT_FALSE(set.contains(entt::entity{0})); + ASSERT_TRUE(set.contains(entt::entity{42})); + ASSERT_EQ(set.index(entt::entity{42}), 0u); + ASSERT_EQ(set.at(0u), entt::entity{42}); + ASSERT_EQ(set.at(1u), static_cast(entt::null)); + ASSERT_EQ(set[0u], entt::entity{42}); + ASSERT_EQ(set.get(entt::entity{42}), nullptr); - set.destroy(42); + set.erase(entt::entity{42}); ASSERT_TRUE(set.empty()); ASSERT_EQ(set.size(), 0u); ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end()); ASSERT_EQ(set.begin(), set.end()); - ASSERT_FALSE(set.has(0)); - ASSERT_FALSE(set.has(42)); + ASSERT_FALSE(set.contains(entt::entity{0})); + ASSERT_FALSE(set.contains(entt::entity{42})); + ASSERT_EQ(set.at(0u), static_cast(entt::null)); + ASSERT_EQ(set.at(1u), static_cast(entt::null)); - set.construct(42); + set.emplace(entt::entity{42}); ASSERT_FALSE(set.empty()); - ASSERT_EQ(set.get(42), 0u); + ASSERT_EQ(set.index(entt::entity{42}), 0u); + ASSERT_EQ(set.at(0u), entt::entity{42}); + ASSERT_EQ(set.at(1u), static_cast(entt::null)); + ASSERT_EQ(set[0u], entt::entity{42}); + + set.clear(); + + ASSERT_TRUE(set.empty()); + ASSERT_EQ(set.size(), 0u); + ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end()); + ASSERT_EQ(set.begin(), set.end()); + ASSERT_FALSE(set.contains(entt::entity{0})); + ASSERT_FALSE(set.contains(entt::entity{42})); + + ASSERT_NO_THROW(set.bind(entt::any{})); +} + +TEST(SparseSet, Contains) { + using traits_type = entt::entt_traits; + + entt::sparse_set set{entt::deletion_policy::in_place}; + + set.emplace(entt::entity{0}); + set.emplace(entt::entity{3}); + set.emplace(entt::entity{42}); + set.emplace(entt::entity{99}); + set.emplace(traits_type::construct(1, 5)); + + ASSERT_FALSE(set.contains(entt::null)); + ASSERT_FALSE(set.contains(entt::tombstone)); + + ASSERT_TRUE(set.contains(entt::entity{0})); + ASSERT_TRUE(set.contains(entt::entity{3})); + ASSERT_TRUE(set.contains(entt::entity{42})); + ASSERT_TRUE(set.contains(entt::entity{99})); + ASSERT_FALSE(set.contains(entt::entity{1})); + ASSERT_TRUE(set.contains(traits_type::construct(1, 5))); + + ASSERT_TRUE(set.contains(traits_type::construct(3, 0))); + ASSERT_FALSE(set.contains(traits_type::construct(42, 1))); + ASSERT_FALSE(set.contains(traits_type::construct(99, traits_type::to_version(entt::tombstone)))); + + set.erase(entt::entity{0}); + set.erase(entt::entity{3}); + + set.remove(entt::entity{42}); + set.remove(entt::entity{99}); + + ASSERT_FALSE(set.contains(entt::null)); + ASSERT_FALSE(set.contains(entt::tombstone)); + + ASSERT_FALSE(set.contains(entt::entity{0})); + ASSERT_FALSE(set.contains(entt::entity{3})); + ASSERT_FALSE(set.contains(entt::entity{42})); + ASSERT_FALSE(set.contains(entt::entity{99})); + ASSERT_FALSE(set.contains(entt::entity{1})); + ASSERT_TRUE(set.contains(traits_type::construct(1, 5))); + + ASSERT_FALSE(set.contains(traits_type::construct(99, traits_type::to_version(entt::tombstone)))); +} + +TEST(SparseSet, Current) { + using traits_type = entt::entt_traits; + + entt::sparse_set set{entt::deletion_policy::in_place}; + + ASSERT_EQ(set.current(traits_type::construct(0, 0)), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(traits_type::construct(3, 3)), traits_type::to_version(entt::tombstone)); + + set.emplace(traits_type::construct(0, 0)); + set.emplace(traits_type::construct(3, 3)); + + ASSERT_NE(set.current(traits_type::construct(0, 0)), traits_type::to_version(entt::tombstone)); + ASSERT_NE(set.current(traits_type::construct(3, 3)), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(traits_type::construct(3, 0)), traits_type::to_version(traits_type::construct(3, 3))); + ASSERT_EQ(set.current(traits_type::construct(42, 1)), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(traits_type::construct(ENTT_SPARSE_PAGE, 1)), traits_type::to_version(entt::tombstone)); + + set.remove(entt::entity{0}); + + ASSERT_EQ(set.current(traits_type::construct(0, 0)), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(traits_type::construct(3, 0)), traits_type::to_version(traits_type::construct(3, 3))); +} + +TEST(SparseSet, Index) { + using traits_type = entt::entt_traits; + + entt::sparse_set set{}; + + set.emplace(traits_type::construct(0, 0)); + set.emplace(traits_type::construct(3, 3)); + + ASSERT_EQ(set.index(traits_type::construct(0, 0)), 0u); + ASSERT_EQ(set.index(traits_type::construct(3, 3)), 1u); + + set.erase(traits_type::construct(0, 0)); + + ASSERT_EQ(set.index(traits_type::construct(3, 3)), 0u); +} + +TEST(SparseSetDeathTest, Index) { + using traits_type = entt::entt_traits; + + entt::sparse_set set{}; + + ASSERT_DEATH(static_cast(set.index(traits_type::construct(3, 0))), ""); + ASSERT_DEATH(static_cast(set.index(entt::null)), ""); +} + +TEST(SparseSet, Move) { + entt::sparse_set set; + set.emplace(entt::entity{42}); ASSERT_TRUE(std::is_move_constructible_v); ASSERT_TRUE(std::is_move_assignable_v); - entt::sparse_set cpy{set}; - set = cpy; + entt::sparse_set other{std::move(set)}; - ASSERT_FALSE(set.empty()); - ASSERT_FALSE(cpy.empty()); - ASSERT_EQ(set.get(42), 0u); - ASSERT_EQ(cpy.get(42), 0u); - - entt::sparse_set other{std::move(set)}; + ASSERT_TRUE(set.empty()); + ASSERT_FALSE(other.empty()); + ASSERT_EQ(set.at(0u), static_cast(entt::null)); + ASSERT_EQ(other.at(0u), entt::entity{42}); set = std::move(other); + + ASSERT_FALSE(set.empty()); + ASSERT_TRUE(other.empty()); + ASSERT_EQ(set.at(0u), entt::entity{42}); + ASSERT_EQ(other.at(0u), static_cast(entt::null)); + + other = entt::sparse_set{}; + other.emplace(entt::entity{3}); other = std::move(set); ASSERT_TRUE(set.empty()); ASSERT_FALSE(other.empty()); - ASSERT_EQ(other.get(42), 0u); + ASSERT_EQ(set.at(0u), static_cast(entt::null)); + ASSERT_EQ(other.at(0u), entt::entity{42}); +} - other.reset(); +TEST(SparseSet, Swap) { + entt::sparse_set set; + entt::sparse_set other{entt::deletion_policy::in_place}; - ASSERT_TRUE(other.empty()); - ASSERT_EQ(other.size(), 0u); - ASSERT_EQ(std::as_const(other).begin(), std::as_const(other).end()); - ASSERT_EQ(other.begin(), other.end()); - ASSERT_FALSE(other.has(0)); - ASSERT_FALSE(other.has(42)); + set.emplace(entt::entity{42}); + + other.emplace(entt::entity{9}); + other.emplace(entt::entity{3}); + other.erase(entt::entity{9}); + + ASSERT_EQ(set.size(), 1u); + ASSERT_EQ(other.size(), 2u); + + set.swap(other); + + ASSERT_EQ(set.size(), 2u); + ASSERT_EQ(other.size(), 1u); + + ASSERT_EQ(set.at(1u), entt::entity{3}); + ASSERT_EQ(other.at(0u), entt::entity{42}); } TEST(SparseSet, Pagination) { - entt::sparse_set set; - constexpr auto entt_per_page = ENTT_PAGE_SIZE / sizeof(std::uint64_t); + entt::sparse_set set; - ASSERT_EQ(set.extent(), 0); + ASSERT_EQ(set.extent(), 0u); - set.construct(entt_per_page-1); + set.emplace(entt::entity{ENTT_SPARSE_PAGE - 1u}); - ASSERT_EQ(set.extent(), entt_per_page); - ASSERT_TRUE(set.has(entt_per_page-1)); + ASSERT_EQ(set.extent(), ENTT_SPARSE_PAGE); + ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE - 1u})); - set.construct(entt_per_page); + set.emplace(entt::entity{ENTT_SPARSE_PAGE}); - ASSERT_EQ(set.extent(), 2 * entt_per_page); - ASSERT_TRUE(set.has(entt_per_page-1)); - ASSERT_TRUE(set.has(entt_per_page)); - ASSERT_FALSE(set.has(entt_per_page+1)); + ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE); + ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE - 1u})); + ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE})); + ASSERT_FALSE(set.contains(entt::entity{ENTT_SPARSE_PAGE + 1u})); - set.destroy(entt_per_page-1); + set.erase(entt::entity{ENTT_SPARSE_PAGE - 1u}); - ASSERT_EQ(set.extent(), 2 * entt_per_page); - ASSERT_FALSE(set.has(entt_per_page-1)); - ASSERT_TRUE(set.has(entt_per_page)); + ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE); + ASSERT_FALSE(set.contains(entt::entity{ENTT_SPARSE_PAGE - 1u})); + ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE})); set.shrink_to_fit(); - set.destroy(entt_per_page); + set.erase(entt::entity{ENTT_SPARSE_PAGE}); - ASSERT_EQ(set.extent(), 2 * entt_per_page); - ASSERT_FALSE(set.has(entt_per_page-1)); - ASSERT_FALSE(set.has(entt_per_page)); + ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE); + ASSERT_FALSE(set.contains(entt::entity{ENTT_SPARSE_PAGE - 1u})); + ASSERT_FALSE(set.contains(entt::entity{ENTT_SPARSE_PAGE})); set.shrink_to_fit(); - ASSERT_EQ(set.extent(), 0); + ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE); +} + +TEST(SparseSet, Emplace) { + entt::sparse_set set{entt::deletion_policy::in_place}; + entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; + + ASSERT_TRUE(set.empty()); + ASSERT_NE(set.emplace(entities[0u]), set.end()); + + set.erase(entities[0u]); + + ASSERT_NE(set.emplace(entities[1u]), set.end()); + ASSERT_NE(set.emplace(entities[0u]), set.end()); + + ASSERT_EQ(set.at(0u), entities[1u]); + ASSERT_EQ(set.at(1u), entities[0u]); + ASSERT_EQ(set.index(entities[0u]), 1u); + ASSERT_EQ(set.index(entities[1u]), 0u); + + set.erase(std::begin(entities), std::end(entities)); + + ASSERT_NE(set.emplace(entities[1u]), set.end()); + ASSERT_NE(set.emplace(entities[0u]), set.end()); + + ASSERT_EQ(set.at(0u), entities[1u]); + ASSERT_EQ(set.at(1u), entities[0u]); + ASSERT_EQ(set.index(entities[0u]), 1u); + ASSERT_EQ(set.index(entities[1u]), 0u); +} + +TEST(SparseSetDeathTest, Emplace) { + entt::sparse_set set{entt::deletion_policy::in_place}; + set.emplace(entt::entity{42}); + + ASSERT_DEATH(set.emplace(entt::entity{42}), ""); +} + +TEST(SparseSet, EmplaceOutOfBounds) { + entt::sparse_set set{entt::deletion_policy::in_place}; + entt::entity entities[2u]{entt::entity{0}, entt::entity{ENTT_SPARSE_PAGE}}; + + ASSERT_NE(set.emplace(entities[0u]), set.end()); + ASSERT_EQ(set.extent(), ENTT_SPARSE_PAGE); + ASSERT_EQ(set.index(entities[0u]), 0u); + + set.erase(entities[0u]); + + ASSERT_NE(set.emplace(entities[1u]), set.end()); + ASSERT_EQ(set.extent(), 2u * ENTT_SPARSE_PAGE); + ASSERT_EQ(set.index(entities[1u]), 0u); +} + +TEST(SparseSet, Bump) { + using traits_type = entt::entt_traits; + + entt::sparse_set set; + entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, traits_type::construct(9, 3)}; + set.insert(std::begin(entities), std::end(entities)); + + ASSERT_EQ(set.current(entities[0u]), 0u); + ASSERT_EQ(set.current(entities[1u]), 0u); + ASSERT_EQ(set.current(entities[2u]), 3u); + + set.bump(entities[0u]); + set.bump(traits_type::construct(traits_type::to_entity(entities[1u]), 1)); + set.bump(traits_type::construct(traits_type::to_entity(entities[2u]), 0)); + + ASSERT_EQ(set.current(entities[0u]), 0u); + ASSERT_EQ(set.current(entities[1u]), 1u); + ASSERT_EQ(set.current(entities[2u]), 0u); +} + +TEST(SparseSetDeathTest, Bump) { + entt::sparse_set set{entt::deletion_policy::in_place}; + + ASSERT_DEATH(set.bump(entt::null), ""); + ASSERT_DEATH(set.bump(entt::tombstone), ""); + ASSERT_DEATH(set.bump(entt::entity{42}), ""); +} + +TEST(SparseSet, Insert) { + entt::sparse_set set{entt::deletion_policy::in_place}; + entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; + + set.emplace(entt::entity{12}); + + ASSERT_EQ(set.insert(std::end(entities), std::end(entities)), set.end()); + ASSERT_NE(set.insert(std::begin(entities), std::end(entities)), set.end()); + + set.emplace(entt::entity{24}); + + ASSERT_TRUE(set.contains(entities[0u])); + ASSERT_TRUE(set.contains(entities[1u])); + ASSERT_FALSE(set.contains(entt::entity{0})); + ASSERT_FALSE(set.contains(entt::entity{9})); + ASSERT_TRUE(set.contains(entt::entity{12})); + ASSERT_TRUE(set.contains(entt::entity{24})); + + ASSERT_FALSE(set.empty()); + ASSERT_EQ(set.size(), 4u); + ASSERT_EQ(set.index(entt::entity{12}), 0u); + ASSERT_EQ(set.index(entities[0u]), 1u); + ASSERT_EQ(set.index(entities[1u]), 2u); + ASSERT_EQ(set.index(entt::entity{24}), 3u); + ASSERT_EQ(set.data()[set.index(entt::entity{12})], entt::entity{12}); + ASSERT_EQ(set.data()[set.index(entities[0u])], entities[0u]); + ASSERT_EQ(set.data()[set.index(entities[1u])], entities[1u]); + ASSERT_EQ(set.data()[set.index(entt::entity{24})], entt::entity{24}); + + set.erase(std::begin(entities), std::end(entities)); + + ASSERT_NE(set.insert(std::rbegin(entities), std::rend(entities)), set.end()); + + ASSERT_EQ(set.size(), 6u); + ASSERT_EQ(set.at(4u), entities[1u]); + ASSERT_EQ(set.at(5u), entities[0u]); + ASSERT_EQ(set.index(entities[0u]), 5u); + ASSERT_EQ(set.index(entities[1u]), 4u); } -TEST(SparseSet, BatchAdd) { - entt::sparse_set set; - entt::sparse_set::entity_type entities[2]; +TEST(SparseSet, Erase) { + using traits_type = entt::entt_traits; + + entt::sparse_set set; + entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, traits_type::construct(9, 3)}; + + ASSERT_EQ(set.policy(), entt::deletion_policy::swap_and_pop); + ASSERT_TRUE(set.empty()); + + set.insert(std::begin(entities), std::end(entities)); + set.erase(set.begin(), set.end()); + + ASSERT_TRUE(set.empty()); + ASSERT_EQ(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); + + set.insert(std::begin(entities), std::end(entities)); + set.erase(entities, entities + 2u); + + ASSERT_FALSE(set.empty()); + ASSERT_EQ(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entities[2u])); + ASSERT_EQ(*set.begin(), entities[2u]); + + set.erase(entities[2u]); + + ASSERT_TRUE(set.empty()); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); + + set.insert(std::begin(entities), std::end(entities)); + std::swap(entities[1u], entities[2u]); + set.erase(entities, entities + 2u); + + ASSERT_FALSE(set.empty()); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entities[2u])); + ASSERT_EQ(*set.begin(), entities[2u]); +} + +TEST(SparseSetDeathTest, Erase) { + using traits_type = entt::entt_traits; + + entt::sparse_set set; + entt::entity entities[2u]{entt::entity{42}, traits_type::construct(9, 3)}; + + ASSERT_DEATH(set.erase(std::begin(entities), std::end(entities)), ""); + ASSERT_DEATH(set.erase(entt::null), ""); +} + +TEST(SparseSet, CrossErase) { + using traits_type = entt::entt_traits; + + entt::sparse_set set; + entt::sparse_set other; + entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; + + set.insert(std::begin(entities), std::end(entities)); + other.emplace(entities[1u]); + set.erase(other.begin(), other.end()); + + ASSERT_TRUE(set.contains(entities[0u])); + ASSERT_FALSE(set.contains(entities[1u])); + ASSERT_EQ(set.data()[0u], entities[0u]); +} + +TEST(SparseSet, StableErase) { + using traits_type = entt::entt_traits; + + entt::sparse_set set{entt::deletion_policy::in_place}; + entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, traits_type::construct(9, 3)}; + + ASSERT_EQ(set.policy(), entt::deletion_policy::in_place); + ASSERT_TRUE(set.empty()); + ASSERT_EQ(set.size(), 0u); + + set.emplace(entities[0u]); + set.emplace(entities[1u]); + set.emplace(entities[2u]); + + set.erase(set.begin(), set.end()); + + ASSERT_FALSE(set.empty()); + ASSERT_EQ(set.size(), 3u); + ASSERT_EQ(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); + ASSERT_TRUE(set.at(0u) == entt::tombstone); + ASSERT_TRUE(set.at(1u) == entt::tombstone); + ASSERT_TRUE(set.at(2u) == entt::tombstone); + + set.emplace(entities[0u]); + set.emplace(entities[1u]); + set.emplace(entities[2u]); + + set.erase(entities, entities + 2u); + + ASSERT_FALSE(set.empty()); + ASSERT_EQ(set.size(), 3u); + ASSERT_EQ(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entities[2u])); + ASSERT_EQ(*set.begin(), entities[2u]); + ASSERT_TRUE(set.at(0u) == entt::tombstone); + ASSERT_TRUE(set.at(1u) == entt::tombstone); + + set.erase(entities[2u]); + + ASSERT_FALSE(set.empty()); + ASSERT_EQ(set.size(), 3u); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); + + set.emplace(entities[0u]); + set.emplace(entities[1u]); + set.emplace(entities[2u]); + + std::swap(entities[1u], entities[2u]); + set.erase(entities, entities + 2u); + + ASSERT_FALSE(set.empty()); + ASSERT_EQ(set.size(), 3u); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entities[2u])); + ASSERT_TRUE(set.at(0u) == entt::tombstone); + ASSERT_EQ(set.at(1u), entities[2u]); + ASSERT_TRUE(set.at(2u) == entt::tombstone); + ASSERT_EQ(*++set.begin(), entities[2u]); + + set.compact(); + + ASSERT_FALSE(set.empty()); + ASSERT_EQ(set.size(), 1u); + ASSERT_EQ(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entities[2u])); + ASSERT_TRUE(set.at(0u) == entities[2u]); + ASSERT_EQ(*set.begin(), entities[2u]); + + set.clear(); + + ASSERT_EQ(set.size(), 0u); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); + + set.emplace(entities[0u]); + set.emplace(entities[1u]); + set.emplace(entities[2u]); + + set.erase(entities[2u]); + + ASSERT_NE(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); + ASSERT_NE(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); + + set.erase(entities[0u]); + set.erase(entities[1u]); + + ASSERT_EQ(set.size(), 3u); + ASSERT_EQ(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); + ASSERT_TRUE(*set.begin() == entt::tombstone); + + set.emplace(entities[0u]); + + ASSERT_EQ(*++set.begin(), entities[0u]); + + set.emplace(entities[1u]); + set.emplace(entities[2u]); + set.emplace(entt::entity{0}); + + ASSERT_EQ(set.size(), 4u); + ASSERT_EQ(*set.begin(), entt::entity{0}); + ASSERT_EQ(set.at(0u), entities[1u]); + ASSERT_EQ(set.at(1u), entities[0u]); + ASSERT_EQ(set.at(2u), entities[2u]); + + ASSERT_NE(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); + ASSERT_NE(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); + ASSERT_NE(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); +} + +TEST(SparseSetDeathTest, StableErase) { + using traits_type = entt::entt_traits; + + entt::sparse_set set{entt::deletion_policy::in_place}; + entt::entity entities[2u]{entt::entity{42}, traits_type::construct(9, 3)}; + + ASSERT_DEATH(set.erase(std::begin(entities), std::end(entities)), ""); + ASSERT_DEATH(set.erase(entt::null), ""); +} + +TEST(SparseSet, CrossStableErase) { + using traits_type = entt::entt_traits; + + entt::sparse_set set{entt::deletion_policy::in_place}; + entt::sparse_set other{entt::deletion_policy::in_place}; + entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; + + set.insert(std::begin(entities), std::end(entities)); + other.emplace(entities[1u]); + set.erase(other.begin(), other.end()); + + ASSERT_TRUE(set.contains(entities[0u])); + ASSERT_FALSE(set.contains(entities[1u])); + ASSERT_EQ(set.data()[0u], entities[0u]); +} + +TEST(SparseSet, Remove) { + using traits_type = entt::entt_traits; + + entt::sparse_set set; + entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, traits_type::construct(9, 3)}; + + ASSERT_EQ(set.policy(), entt::deletion_policy::swap_and_pop); + ASSERT_TRUE(set.empty()); + + ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 0u); + ASSERT_EQ(set.remove(entities[1u]), 0u); + + ASSERT_TRUE(set.empty()); + + set.insert(std::begin(entities), std::end(entities)); + + ASSERT_EQ(set.remove(set.begin(), set.end()), 3u); + ASSERT_TRUE(set.empty()); + ASSERT_EQ(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); + + set.insert(std::begin(entities), std::end(entities)); + + ASSERT_EQ(set.remove(entities, entities + 2u), 2u); + ASSERT_FALSE(set.empty()); + ASSERT_EQ(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entities[2u])); + ASSERT_EQ(*set.begin(), entities[2u]); + + ASSERT_EQ(set.remove(entities[2u]), 1u); + ASSERT_EQ(set.remove(entities[2u]), 0u); + ASSERT_TRUE(set.empty()); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); + + set.insert(entities, entities + 2u); + + ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 2u); + ASSERT_EQ(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); + ASSERT_TRUE(set.empty()); + + set.insert(std::begin(entities), std::end(entities)); + std::swap(entities[1u], entities[2u]); + + ASSERT_EQ(set.remove(entities, entities + 2u), 2u); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entities[2u])); + ASSERT_FALSE(set.empty()); + ASSERT_EQ(*set.begin(), entities[2u]); + + ASSERT_EQ(set.remove(traits_type::construct(9, 0)), 0u); + ASSERT_EQ(set.remove(entt::tombstone), 0u); + ASSERT_EQ(set.remove(entt::null), 0u); +} + +TEST(SparseSet, CrossRemove) { + using traits_type = entt::entt_traits; + + entt::sparse_set set; + entt::sparse_set other; + entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; + + set.insert(std::begin(entities), std::end(entities)); + other.emplace(entities[1u]); + set.remove(other.begin(), other.end()); + + ASSERT_TRUE(set.contains(entities[0u])); + ASSERT_FALSE(set.contains(entities[1u])); + ASSERT_EQ(set.data()[0u], entities[0u]); +} + +TEST(SparseSet, StableRemove) { + using traits_type = entt::entt_traits; + + entt::sparse_set set{entt::deletion_policy::in_place}; + entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, traits_type::construct(9, 3)}; + + ASSERT_EQ(set.policy(), entt::deletion_policy::in_place); + ASSERT_TRUE(set.empty()); + ASSERT_EQ(set.size(), 0u); + + ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 0u); + ASSERT_EQ(set.remove(entities[1u]), 0u); + + ASSERT_TRUE(set.empty()); + ASSERT_EQ(set.size(), 0u); + + set.emplace(entities[0u]); + set.emplace(entities[1u]); + set.emplace(entities[2u]); + + ASSERT_EQ(set.remove(set.begin(), set.end()), 3u); + ASSERT_EQ(set.remove(set.begin(), set.end()), 0u); - entities[0] = 3; - entities[1] = 42; + ASSERT_FALSE(set.empty()); + ASSERT_EQ(set.size(), 3u); + ASSERT_EQ(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); + ASSERT_TRUE(set.at(0u) == entt::tombstone); + ASSERT_TRUE(set.at(1u) == entt::tombstone); + ASSERT_TRUE(set.at(2u) == entt::tombstone); + + set.emplace(entities[0u]); + set.emplace(entities[1u]); + set.emplace(entities[2u]); + + ASSERT_EQ(set.remove(entities, entities + 2u), 2u); + ASSERT_EQ(set.remove(entities, entities + 2u), 0u); + + ASSERT_FALSE(set.empty()); + ASSERT_EQ(set.size(), 3u); + ASSERT_EQ(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entities[2u])); + ASSERT_EQ(*set.begin(), entities[2u]); + ASSERT_TRUE(set.at(0u) == entt::tombstone); + ASSERT_TRUE(set.at(1u) == entt::tombstone); + + ASSERT_EQ(set.remove(entities[2u]), 1u); + ASSERT_EQ(set.remove(entities[2u]), 0u); + + ASSERT_FALSE(set.empty()); + ASSERT_EQ(set.size(), 3u); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); + + set.emplace(entities[0u]); + set.emplace(entities[1u]); + set.emplace(entities[2u]); - set.construct(12); - set.batch(std::begin(entities), std::end(entities)); - set.construct(24); + std::swap(entities[1u], entities[2u]); - ASSERT_TRUE(set.has(entities[0])); - ASSERT_TRUE(set.has(entities[1])); - ASSERT_FALSE(set.has(0)); - ASSERT_FALSE(set.has(9)); - ASSERT_TRUE(set.has(12)); - ASSERT_TRUE(set.has(24)); + ASSERT_EQ(set.remove(entities, entities + 2u), 2u); + ASSERT_EQ(set.remove(entities, entities + 2u), 0u); ASSERT_FALSE(set.empty()); + ASSERT_EQ(set.size(), 3u); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entities[2u])); + ASSERT_TRUE(set.at(0u) == entt::tombstone); + ASSERT_EQ(set.at(1u), entities[2u]); + ASSERT_TRUE(set.at(2u) == entt::tombstone); + ASSERT_EQ(*++set.begin(), entities[2u]); + + set.compact(); + + ASSERT_FALSE(set.empty()); + ASSERT_EQ(set.size(), 1u); + ASSERT_EQ(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entities[2u])); + ASSERT_TRUE(set.at(0u) == entities[2u]); + ASSERT_EQ(*set.begin(), entities[2u]); + + set.clear(); + + ASSERT_EQ(set.size(), 0u); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); + + set.emplace(entities[0u]); + set.emplace(entities[1u]); + set.emplace(entities[2u]); + + ASSERT_EQ(set.remove(entities[2u]), 1u); + ASSERT_EQ(set.remove(entities[2u]), 0u); + + ASSERT_NE(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); + ASSERT_NE(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); + + ASSERT_EQ(set.remove(entities[0u]), 1u); + ASSERT_EQ(set.remove(entities[1u]), 1u); + ASSERT_EQ(set.remove(entities, entities + 2u), 0u); + + ASSERT_EQ(set.size(), 3u); + ASSERT_EQ(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); + ASSERT_EQ(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); + ASSERT_TRUE(*set.begin() == entt::tombstone); + + set.emplace(entities[0u]); + + ASSERT_EQ(*++set.begin(), entities[0u]); + + set.emplace(entities[1u]); + set.emplace(entities[2u]); + set.emplace(entt::entity{0}); + ASSERT_EQ(set.size(), 4u); - ASSERT_EQ(set.get(12), 0u); - ASSERT_EQ(set.get(entities[0]), 1u); - ASSERT_EQ(set.get(entities[1]), 2u); - ASSERT_EQ(set.get(24), 3u); + ASSERT_EQ(*set.begin(), entt::entity{0}); + ASSERT_EQ(set.at(0u), entities[1u]); + ASSERT_EQ(set.at(1u), entities[0u]); + ASSERT_EQ(set.at(2u), entities[2u]); + + ASSERT_NE(set.current(entities[0u]), traits_type::to_version(entt::tombstone)); + ASSERT_NE(set.current(entities[1u]), traits_type::to_version(entt::tombstone)); + ASSERT_NE(set.current(entities[2u]), traits_type::to_version(entt::tombstone)); + + ASSERT_EQ(set.remove(traits_type::construct(9, 0)), 0u); + ASSERT_EQ(set.remove(entt::null), 0u); +} + +TEST(SparseSet, CrossStableRemove) { + using traits_type = entt::entt_traits; + + entt::sparse_set set{entt::deletion_policy::in_place}; + entt::sparse_set other{entt::deletion_policy::in_place}; + entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; + + set.insert(std::begin(entities), std::end(entities)); + other.emplace(entities[1u]); + set.remove(other.begin(), other.end()); + + ASSERT_TRUE(set.contains(entities[0u])); + ASSERT_FALSE(set.contains(entities[1u])); + ASSERT_EQ(set.data()[0u], entities[0u]); +} + +TEST(SparseSet, Compact) { + entt::sparse_set set{entt::deletion_policy::in_place}; + + ASSERT_TRUE(set.empty()); + ASSERT_EQ(set.size(), 0u); + + set.compact(); + + ASSERT_TRUE(set.empty()); + ASSERT_EQ(set.size(), 0u); + + set.emplace(entt::entity{0}); + set.compact(); + + ASSERT_FALSE(set.empty()); + ASSERT_EQ(set.size(), 1u); + + set.emplace(entt::entity{42}); + set.erase(entt::entity{0}); + + ASSERT_EQ(set.size(), 2u); + ASSERT_EQ(set.index(entt::entity{42}), 1u); + + set.compact(); + + ASSERT_EQ(set.size(), 1u); + ASSERT_EQ(set.index(entt::entity{42}), 0u); + + set.emplace(entt::entity{0}); + set.compact(); + + ASSERT_EQ(set.size(), 2u); + ASSERT_EQ(set.index(entt::entity{42}), 0u); + ASSERT_EQ(set.index(entt::entity{0}), 1u); + + set.erase(entt::entity{0}); + set.erase(entt::entity{42}); + set.compact(); + + ASSERT_TRUE(set.empty()); +} + +TEST(SparseSet, SwapEntity) { + using traits_type = entt::entt_traits; + + entt::sparse_set set; + + set.emplace(traits_type::construct(3, 5)); + set.emplace(traits_type::construct(42, 99)); + + ASSERT_EQ(set.index(traits_type::construct(3, 5)), 0u); + ASSERT_EQ(set.index(traits_type::construct(42, 99)), 1u); + + set.swap_elements(traits_type::construct(3, 5), traits_type::construct(42, 99)); + + ASSERT_EQ(set.index(traits_type::construct(3, 5)), 1u); + ASSERT_EQ(set.index(traits_type::construct(42, 99)), 0u); + + set.swap_elements(traits_type::construct(3, 5), traits_type::construct(42, 99)); + + ASSERT_EQ(set.index(traits_type::construct(3, 5)), 0u); + ASSERT_EQ(set.index(traits_type::construct(42, 99)), 1u); +} + +TEST(SparseSetDeathTest, SwapEntity) { + entt::sparse_set set; + + ASSERT_TRUE(set.empty()); + ASSERT_DEATH(set.swap_elements(entt::entity{0}, entt::entity{1}), ""); +} + +TEST(SparseSet, Clear) { + entt::sparse_set set{entt::deletion_policy::in_place}; + + set.emplace(entt::entity{3}); + set.emplace(entt::entity{42}); + set.emplace(entt::entity{9}); + set.erase(entt::entity{42}); + + ASSERT_FALSE(set.empty()); + ASSERT_EQ(set.size(), 3u); + ASSERT_EQ(*set.begin(), entt::entity{9}); + + set.clear(); + + ASSERT_TRUE(set.empty()); + ASSERT_EQ(set.size(), 0u); + + ASSERT_EQ(set.find(entt::entity{3}), set.end()); + ASSERT_EQ(set.find(entt::entity{9}), set.end()); + + set.emplace(entt::entity{3}); + set.emplace(entt::entity{42}); + set.emplace(entt::entity{9}); + + ASSERT_FALSE(set.empty()); + ASSERT_EQ(set.size(), 3u); + ASSERT_EQ(*set.begin(), entt::entity{9}); + + set.clear(); + + ASSERT_TRUE(set.empty()); + ASSERT_EQ(set.size(), 0u); + + ASSERT_EQ(set.find(entt::entity{3}), set.end()); + ASSERT_EQ(set.find(entt::entity{42}), set.end()); + ASSERT_EQ(set.find(entt::entity{9}), set.end()); } TEST(SparseSet, Iterator) { - using iterator_type = typename entt::sparse_set::iterator_type; + using iterator = typename entt::sparse_set::iterator; - entt::sparse_set set; - set.construct(3); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); - iterator_type end{set.begin()}; - iterator_type begin{}; + entt::sparse_set set; + set.emplace(entt::entity{3}); + + iterator end{set.begin()}; + iterator begin{}; begin = set.end(); std::swap(begin, end); - ASSERT_EQ(begin, set.begin()); - ASSERT_EQ(end, set.end()); + ASSERT_EQ(begin, set.cbegin()); + ASSERT_EQ(end, set.cend()); ASSERT_NE(begin, end); + ASSERT_EQ(begin.index(), 0); + ASSERT_EQ(end.index(), -1); + ASSERT_EQ(begin++, set.begin()); ASSERT_EQ(begin--, set.end()); - ASSERT_EQ(begin+1, set.end()); - ASSERT_EQ(end-1, set.begin()); + ASSERT_EQ(begin + 1, set.end()); + ASSERT_EQ(end - 1, set.begin()); ASSERT_EQ(++begin, set.end()); ASSERT_EQ(--begin, set.begin()); @@ -172,7 +952,7 @@ TEST(SparseSet, Iterator) { ASSERT_EQ(end - (end - begin), set.begin()); ASSERT_EQ(end + (begin - end), set.begin()); - ASSERT_EQ(begin[0], *set.begin()); + ASSERT_EQ(begin[0u], *set.begin()); ASSERT_LT(begin, end); ASSERT_LE(begin, set.begin()); @@ -180,218 +960,464 @@ TEST(SparseSet, Iterator) { ASSERT_GT(end, begin); ASSERT_GE(end, set.end()); - ASSERT_EQ(*begin, 3); - ASSERT_EQ(*begin.operator->(), 3); + ASSERT_EQ(*begin, entt::entity{3}); + ASSERT_EQ(*begin.operator->(), entt::entity{3}); + + ASSERT_EQ(begin.index(), 0); + ASSERT_EQ(end.index(), -1); + + set.emplace(entt::entity{42}); + begin = set.begin(); + + ASSERT_EQ(begin.index(), 1); + ASSERT_EQ(end.index(), -1); + + ASSERT_EQ(begin[0u], entt::entity{42}); + ASSERT_EQ(begin[1u], entt::entity{3}); } -TEST(SparseSet, Find) { - entt::sparse_set set; - set.construct(3); - set.construct(42); - set.construct(99); +TEST(SparseSet, ReverseIterator) { + using reverse_iterator = typename entt::sparse_set::reverse_iterator; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + entt::sparse_set set; + set.emplace(entt::entity{3}); + + reverse_iterator end{set.rbegin()}; + reverse_iterator begin{}; + begin = set.rend(); + std::swap(begin, end); + + ASSERT_EQ(begin, set.crbegin()); + ASSERT_EQ(end, set.crend()); + ASSERT_NE(begin, end); + + ASSERT_EQ(begin.base().index(), -1); + ASSERT_EQ(end.base().index(), 0); + + ASSERT_EQ(begin++, set.rbegin()); + ASSERT_EQ(begin--, set.rend()); + + ASSERT_EQ(begin + 1, set.rend()); + ASSERT_EQ(end - 1, set.rbegin()); + + ASSERT_EQ(++begin, set.rend()); + ASSERT_EQ(--begin, set.rbegin()); + + ASSERT_EQ(begin += 1, set.rend()); + ASSERT_EQ(begin -= 1, set.rbegin()); + + ASSERT_EQ(begin + (end - begin), set.rend()); + ASSERT_EQ(begin - (begin - end), set.rend()); - ASSERT_NE(set.find(3), set.end()); - ASSERT_NE(set.find(42), set.end()); - ASSERT_NE(set.find(99), set.end()); - ASSERT_EQ(set.find(0), set.end()); + ASSERT_EQ(end - (end - begin), set.rbegin()); + ASSERT_EQ(end + (begin - end), set.rbegin()); - auto it = set.find(99); + ASSERT_EQ(begin[0u], *set.rbegin()); - ASSERT_EQ(*it, 99); - ASSERT_EQ(*(++it), 42); - ASSERT_EQ(*(++it), 3); + ASSERT_LT(begin, end); + ASSERT_LE(begin, set.rbegin()); + + ASSERT_GT(end, begin); + ASSERT_GE(end, set.rend()); + + ASSERT_EQ(*begin, entt::entity{3}); + ASSERT_EQ(*begin.operator->(), entt::entity{3}); + + ASSERT_EQ(begin.base().index(), -1); + ASSERT_EQ(end.base().index(), 0); + + set.emplace(entt::entity{42}); + end = set.rend(); + + ASSERT_EQ(begin.base().index(), -1); + ASSERT_EQ(end.base().index(), 1); + + ASSERT_EQ(begin[0u], entt::entity{3}); + ASSERT_EQ(begin[1u], entt::entity{42}); +} + +TEST(SparseSet, Find) { + using traits_type = entt::entt_traits; + + entt::sparse_set set; + set.emplace(entt::entity{3}); + set.emplace(entt::entity{42}); + set.emplace(traits_type::construct(99, 1)); + + ASSERT_NE(set.find(entt::entity{3}), set.end()); + ASSERT_NE(set.find(entt::entity{42}), set.end()); + ASSERT_NE(set.find(traits_type::construct(99, 1)), set.end()); + ASSERT_EQ(set.find(traits_type::construct(99, 5)), set.end()); + ASSERT_EQ(set.find(entt::entity{0}), set.end()); + ASSERT_EQ(set.find(entt::tombstone), set.end()); + ASSERT_EQ(set.find(entt::null), set.end()); + + auto it = set.find(traits_type::construct(99, 1)); + + ASSERT_EQ(*it, traits_type::construct(99, 1)); + ASSERT_EQ(*(++it), entt::entity{42}); + ASSERT_EQ(*(++it), entt::entity{3}); ASSERT_EQ(++it, set.end()); - ASSERT_EQ(++set.find(3), set.end()); + ASSERT_EQ(++set.find(entt::entity{3}), set.end()); } TEST(SparseSet, Data) { - entt::sparse_set set; + entt::sparse_set set; + + set.emplace(entt::entity{3}); + set.emplace(entt::entity{12}); + set.emplace(entt::entity{42}); + + ASSERT_EQ(set.index(entt::entity{3}), 0u); + ASSERT_EQ(set.index(entt::entity{12}), 1u); + ASSERT_EQ(set.index(entt::entity{42}), 2u); + + ASSERT_EQ(set.data()[0u], entt::entity{3}); + ASSERT_EQ(set.data()[1u], entt::entity{12}); + ASSERT_EQ(set.data()[2u], entt::entity{42}); +} + +TEST(SparseSet, SortOrdered) { + entt::sparse_set set; + entt::entity entities[5u]{entt::entity{42}, entt::entity{12}, entt::entity{9}, entt::entity{7}, entt::entity{3}}; + + set.insert(std::begin(entities), std::end(entities)); + set.sort(std::less{}); + + ASSERT_TRUE(std::equal(std::rbegin(entities), std::rend(entities), set.begin(), set.end())); +} + +TEST(SparseSet, SortReverse) { + entt::sparse_set set; + entt::entity entities[5u]{entt::entity{3}, entt::entity{7}, entt::entity{9}, entt::entity{12}, entt::entity{42}}; + + set.insert(std::begin(entities), std::end(entities)); + set.sort(std::less{}); + + ASSERT_TRUE(std::equal(std::begin(entities), std::end(entities), set.begin(), set.end())); +} + +TEST(SparseSet, SortUnordered) { + entt::sparse_set set; + entt::entity entities[5u]{entt::entity{9}, entt::entity{7}, entt::entity{3}, entt::entity{12}, entt::entity{42}}; + + set.insert(std::begin(entities), std::end(entities)); + set.sort(std::less{}); + + auto begin = set.begin(); + auto end = set.end(); - set.construct(3); - set.construct(12); - set.construct(42); + ASSERT_EQ(*(begin++), entities[2u]); + ASSERT_EQ(*(begin++), entities[1u]); + ASSERT_EQ(*(begin++), entities[0u]); + ASSERT_EQ(*(begin++), entities[3u]); + ASSERT_EQ(*(begin++), entities[4u]); + ASSERT_EQ(begin, end); +} + +TEST(SparseSet, SortRange) { + entt::sparse_set set{entt::deletion_policy::in_place}; + entt::entity entities[5u]{entt::entity{7}, entt::entity{9}, entt::entity{3}, entt::entity{12}, entt::entity{42}}; + + set.insert(std::begin(entities), std::end(entities)); + set.erase(entities[0u]); + + ASSERT_EQ(set.size(), 5u); + + set.sort(std::less{}); + + ASSERT_EQ(set.size(), 4u); + ASSERT_EQ(set[0u], entities[4u]); + ASSERT_EQ(set[1u], entities[3u]); + ASSERT_EQ(set[2u], entities[1u]); + ASSERT_EQ(set[3u], entities[2u]); + + set.clear(); + set.compact(); + set.insert(std::begin(entities), std::end(entities)); + set.sort_n(0u, std::less{}); + + ASSERT_TRUE(std::equal(std::rbegin(entities), std::rend(entities), set.begin(), set.end())); + + set.sort_n(2u, std::less{}); + + ASSERT_EQ(set.data()[0u], entities[1u]); + ASSERT_EQ(set.data()[1u], entities[0u]); + ASSERT_EQ(set.data()[2u], entities[2u]); + + set.sort_n(5u, std::less{}); + + auto begin = set.begin(); + auto end = set.end(); - ASSERT_EQ(set.get(3), 0u); - ASSERT_EQ(set.get(12), 1u); - ASSERT_EQ(set.get(42), 2u); + ASSERT_EQ(*(begin++), entities[2u]); + ASSERT_EQ(*(begin++), entities[0u]); + ASSERT_EQ(*(begin++), entities[1u]); + ASSERT_EQ(*(begin++), entities[3u]); + ASSERT_EQ(*(begin++), entities[4u]); + ASSERT_EQ(begin, end); +} + +TEST(SparseSetDeathTest, SortRange) { + entt::sparse_set set{entt::deletion_policy::in_place}; + entt::entity entity{42}; - ASSERT_EQ(*(set.data() + 0u), 3u); - ASSERT_EQ(*(set.data() + 1u), 12u); - ASSERT_EQ(*(set.data() + 2u), 42u); + set.emplace(entity); + set.erase(entity); + + ASSERT_DEATH(set.sort_n(0u, std::less{});, ""); + ASSERT_DEATH(set.sort_n(3u, std::less{});, ""); } TEST(SparseSet, RespectDisjoint) { - entt::sparse_set lhs; - entt::sparse_set rhs; + entt::sparse_set lhs; + entt::sparse_set rhs; - lhs.construct(3); - lhs.construct(12); - lhs.construct(42); + entt::entity lhs_entities[3u]{entt::entity{3}, entt::entity{12}, entt::entity{42}}; + lhs.insert(std::begin(lhs_entities), std::end(lhs_entities)); - ASSERT_EQ(lhs.get(3), 0u); - ASSERT_EQ(lhs.get(12), 1u); - ASSERT_EQ(lhs.get(42), 2u); + ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end())); lhs.respect(rhs); - ASSERT_EQ(std::as_const(lhs).get(3), 0u); - ASSERT_EQ(std::as_const(lhs).get(12), 1u); - ASSERT_EQ(std::as_const(lhs).get(42), 2u); + ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end())); } TEST(SparseSet, RespectOverlap) { - entt::sparse_set lhs; - entt::sparse_set rhs; + entt::sparse_set lhs; + entt::sparse_set rhs; - lhs.construct(3); - lhs.construct(12); - lhs.construct(42); + entt::entity lhs_entities[3u]{entt::entity{3}, entt::entity{12}, entt::entity{42}}; + lhs.insert(std::begin(lhs_entities), std::end(lhs_entities)); - rhs.construct(12); + entt::entity rhs_entities[1u]{entt::entity{12}}; + rhs.insert(std::begin(rhs_entities), std::end(rhs_entities)); - ASSERT_EQ(lhs.get(3), 0u); - ASSERT_EQ(lhs.get(12), 1u); - ASSERT_EQ(lhs.get(42), 2u); + ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end())); + ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end())); lhs.respect(rhs); - ASSERT_EQ(std::as_const(lhs).get(3), 0u); - ASSERT_EQ(std::as_const(lhs).get(12), 2u); - ASSERT_EQ(std::as_const(lhs).get(42), 1u); + auto begin = lhs.begin(); + auto end = lhs.end(); + + ASSERT_EQ(*(begin++), lhs_entities[1u]); + ASSERT_EQ(*(begin++), lhs_entities[2u]); + ASSERT_EQ(*(begin++), lhs_entities[0u]); + ASSERT_EQ(begin, end); } TEST(SparseSet, RespectOrdered) { - entt::sparse_set lhs; - entt::sparse_set rhs; - - lhs.construct(1); - lhs.construct(2); - lhs.construct(3); - lhs.construct(4); - lhs.construct(5); - - ASSERT_EQ(lhs.get(1), 0u); - ASSERT_EQ(lhs.get(2), 1u); - ASSERT_EQ(lhs.get(3), 2u); - ASSERT_EQ(lhs.get(4), 3u); - ASSERT_EQ(lhs.get(5), 4u); - - rhs.construct(6); - rhs.construct(1); - rhs.construct(2); - rhs.construct(3); - rhs.construct(4); - rhs.construct(5); - - ASSERT_EQ(rhs.get(6), 0u); - ASSERT_EQ(rhs.get(1), 1u); - ASSERT_EQ(rhs.get(2), 2u); - ASSERT_EQ(rhs.get(3), 3u); - ASSERT_EQ(rhs.get(4), 4u); - ASSERT_EQ(rhs.get(5), 5u); + entt::sparse_set lhs; + entt::sparse_set rhs; + + entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}}; + lhs.insert(std::begin(lhs_entities), std::end(lhs_entities)); + + entt::entity rhs_entities[6u]{entt::entity{6}, entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}}; + rhs.insert(std::begin(rhs_entities), std::end(rhs_entities)); + + ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end())); + ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end())); rhs.respect(lhs); - ASSERT_EQ(rhs.get(6), 0u); - ASSERT_EQ(rhs.get(1), 1u); - ASSERT_EQ(rhs.get(2), 2u); - ASSERT_EQ(rhs.get(3), 3u); - ASSERT_EQ(rhs.get(4), 4u); - ASSERT_EQ(rhs.get(5), 5u); + ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end())); } TEST(SparseSet, RespectReverse) { - entt::sparse_set lhs; - entt::sparse_set rhs; - - lhs.construct(1); - lhs.construct(2); - lhs.construct(3); - lhs.construct(4); - lhs.construct(5); - - ASSERT_EQ(lhs.get(1), 0u); - ASSERT_EQ(lhs.get(2), 1u); - ASSERT_EQ(lhs.get(3), 2u); - ASSERT_EQ(lhs.get(4), 3u); - ASSERT_EQ(lhs.get(5), 4u); - - rhs.construct(5); - rhs.construct(4); - rhs.construct(3); - rhs.construct(2); - rhs.construct(1); - rhs.construct(6); - - ASSERT_EQ(rhs.get(5), 0u); - ASSERT_EQ(rhs.get(4), 1u); - ASSERT_EQ(rhs.get(3), 2u); - ASSERT_EQ(rhs.get(2), 3u); - ASSERT_EQ(rhs.get(1), 4u); - ASSERT_EQ(rhs.get(6), 5u); + entt::sparse_set lhs; + entt::sparse_set rhs; + + entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}}; + lhs.insert(std::begin(lhs_entities), std::end(lhs_entities)); + + entt::entity rhs_entities[6u]{entt::entity{5}, entt::entity{4}, entt::entity{3}, entt::entity{2}, entt::entity{1}, entt::entity{6}}; + rhs.insert(std::begin(rhs_entities), std::end(rhs_entities)); + + ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end())); + ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end())); rhs.respect(lhs); - ASSERT_EQ(rhs.get(6), 0u); - ASSERT_EQ(rhs.get(1), 1u); - ASSERT_EQ(rhs.get(2), 2u); - ASSERT_EQ(rhs.get(3), 3u); - ASSERT_EQ(rhs.get(4), 4u); - ASSERT_EQ(rhs.get(5), 5u); + auto begin = rhs.begin(); + auto end = rhs.end(); + + ASSERT_EQ(*(begin++), rhs_entities[0u]); + ASSERT_EQ(*(begin++), rhs_entities[1u]); + ASSERT_EQ(*(begin++), rhs_entities[2u]); + ASSERT_EQ(*(begin++), rhs_entities[3u]); + ASSERT_EQ(*(begin++), rhs_entities[4u]); + ASSERT_EQ(*(begin++), rhs_entities[5u]); + ASSERT_EQ(begin, end); } TEST(SparseSet, RespectUnordered) { - entt::sparse_set lhs; - entt::sparse_set rhs; - - lhs.construct(1); - lhs.construct(2); - lhs.construct(3); - lhs.construct(4); - lhs.construct(5); - - ASSERT_EQ(lhs.get(1), 0u); - ASSERT_EQ(lhs.get(2), 1u); - ASSERT_EQ(lhs.get(3), 2u); - ASSERT_EQ(lhs.get(4), 3u); - ASSERT_EQ(lhs.get(5), 4u); - - rhs.construct(3); - rhs.construct(2); - rhs.construct(6); - rhs.construct(1); - rhs.construct(4); - rhs.construct(5); - - ASSERT_EQ(rhs.get(3), 0u); - ASSERT_EQ(rhs.get(2), 1u); - ASSERT_EQ(rhs.get(6), 2u); - ASSERT_EQ(rhs.get(1), 3u); - ASSERT_EQ(rhs.get(4), 4u); - ASSERT_EQ(rhs.get(5), 5u); + entt::sparse_set lhs; + entt::sparse_set rhs; + + entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}}; + lhs.insert(std::begin(lhs_entities), std::end(lhs_entities)); + + entt::entity rhs_entities[6u]{entt::entity{3}, entt::entity{2}, entt::entity{6}, entt::entity{1}, entt::entity{4}, entt::entity{5}}; + rhs.insert(std::begin(rhs_entities), std::end(rhs_entities)); + + ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end())); + ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end())); rhs.respect(lhs); - ASSERT_EQ(rhs.get(6), 0u); - ASSERT_EQ(rhs.get(1), 1u); - ASSERT_EQ(rhs.get(2), 2u); - ASSERT_EQ(rhs.get(3), 3u); - ASSERT_EQ(rhs.get(4), 4u); - ASSERT_EQ(rhs.get(5), 5u); + auto begin = rhs.begin(); + auto end = rhs.end(); + + ASSERT_EQ(*(begin++), rhs_entities[5u]); + ASSERT_EQ(*(begin++), rhs_entities[4u]); + ASSERT_EQ(*(begin++), rhs_entities[0u]); + ASSERT_EQ(*(begin++), rhs_entities[1u]); + ASSERT_EQ(*(begin++), rhs_entities[3u]); + ASSERT_EQ(*(begin++), rhs_entities[2u]); + ASSERT_EQ(begin, end); +} + +TEST(SparseSet, RespectInvalid) { + using traits_type = entt::entt_traits; + + entt::sparse_set lhs; + entt::sparse_set rhs; + + entt::entity lhs_entities[3u]{entt::entity{1}, entt::entity{2}, traits_type::construct(3, 1)}; + lhs.insert(std::begin(lhs_entities), std::end(lhs_entities)); + + entt::entity rhs_entities[3u]{entt::entity{2}, entt::entity{1}, traits_type::construct(3, 2)}; + rhs.insert(std::begin(rhs_entities), std::end(rhs_entities)); + + ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.begin(), lhs.end())); + ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.begin(), rhs.end())); + + rhs.respect(lhs); + + auto begin = rhs.begin(); + auto end = rhs.end(); + + ASSERT_EQ(*(begin++), rhs_entities[0u]); + ASSERT_EQ(*(begin++), rhs_entities[1u]); + ASSERT_EQ(*(begin++), rhs_entities[2u]); + ASSERT_EQ(rhs.current(rhs_entities[0u]), 0u); + ASSERT_EQ(rhs.current(rhs_entities[1u]), 0u); + ASSERT_EQ(rhs.current(rhs_entities[2u]), 2u); + ASSERT_EQ(begin, end); } TEST(SparseSet, CanModifyDuringIteration) { - entt::sparse_set set; - set.construct(0); + entt::sparse_set set; + set.emplace(entt::entity{0}); - ASSERT_EQ(set.capacity(), entt::sparse_set::size_type{1}); + ASSERT_EQ(set.capacity(), 1u); const auto it = set.begin(); - set.reserve(entt::sparse_set::size_type{2}); + set.reserve(2u); - ASSERT_EQ(set.capacity(), entt::sparse_set::size_type{2}); + ASSERT_EQ(set.capacity(), 2u); // this should crash with asan enabled if we break the constraint - const auto entity = *it; - (void)entity; + [[maybe_unused]] const auto entity = *it; +} + +TEST(SparseSet, CustomAllocator) { + test::throwing_allocator allocator{}; + entt::basic_sparse_set> set{allocator}; + + ASSERT_EQ(set.get_allocator(), allocator); + + set.reserve(1u); + + ASSERT_EQ(set.capacity(), 1u); + + set.emplace(entt::entity{0}); + set.emplace(entt::entity{1}); + + entt::basic_sparse_set> other{std::move(set), allocator}; + + ASSERT_TRUE(set.empty()); + ASSERT_FALSE(other.empty()); + ASSERT_EQ(set.capacity(), 0u); + ASSERT_EQ(other.capacity(), 2u); + ASSERT_EQ(other.size(), 2u); + + set = std::move(other); + + ASSERT_FALSE(set.empty()); + ASSERT_TRUE(other.empty()); + ASSERT_EQ(other.capacity(), 0u); + ASSERT_EQ(set.capacity(), 2u); + ASSERT_EQ(set.size(), 2u); + + set.swap(other); + set = std::move(other); + + ASSERT_FALSE(set.empty()); + ASSERT_TRUE(other.empty()); + ASSERT_EQ(other.capacity(), 0u); + ASSERT_EQ(set.capacity(), 2u); + ASSERT_EQ(set.size(), 2u); + + set.clear(); + + ASSERT_EQ(set.capacity(), 2u); + ASSERT_EQ(set.size(), 0u); + + set.shrink_to_fit(); + + ASSERT_EQ(set.capacity(), 0u); +} + +TEST(SparseSet, ThrowingAllocator) { + entt::basic_sparse_set> set{}; + + test::throwing_allocator::trigger_on_allocate = true; + + ASSERT_THROW(set.reserve(1u), test::throwing_allocator::exception_type); + ASSERT_EQ(set.capacity(), 0u); + ASSERT_EQ(set.extent(), 0u); + + test::throwing_allocator::trigger_on_allocate = true; + + ASSERT_THROW(set.emplace(entt::entity{0}), test::throwing_allocator::exception_type); + ASSERT_EQ(set.extent(), ENTT_SPARSE_PAGE); + ASSERT_EQ(set.capacity(), 0u); + + set.emplace(entt::entity{0}); + test::throwing_allocator::trigger_on_allocate = true; + + ASSERT_THROW(set.reserve(2u), test::throwing_allocator::exception_type); + ASSERT_EQ(set.extent(), ENTT_SPARSE_PAGE); + ASSERT_TRUE(set.contains(entt::entity{0})); + ASSERT_EQ(set.capacity(), 1u); + + test::throwing_allocator::trigger_on_allocate = true; + + ASSERT_THROW(set.emplace(entt::entity{1}), test::throwing_allocator::exception_type); + ASSERT_EQ(set.extent(), ENTT_SPARSE_PAGE); + ASSERT_TRUE(set.contains(entt::entity{0})); + ASSERT_FALSE(set.contains(entt::entity{1})); + ASSERT_EQ(set.capacity(), 1u); + + entt::entity entities[2u]{entt::entity{1}, entt::entity{ENTT_SPARSE_PAGE}}; + test::throwing_allocator::trigger_after_allocate = true; + + ASSERT_THROW(set.insert(std::begin(entities), std::end(entities)), test::throwing_allocator::exception_type); + ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE); + ASSERT_TRUE(set.contains(entt::entity{0})); + ASSERT_TRUE(set.contains(entt::entity{1})); + ASSERT_FALSE(set.contains(entt::entity{ENTT_SPARSE_PAGE})); + ASSERT_EQ(set.capacity(), 2u); + ASSERT_EQ(set.size(), 2u); + + set.emplace(entities[1u]); + + ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE})); } diff --git a/modules/entt/test/entt/entity/storage.cpp b/modules/entt/test/entt/entity/storage.cpp index 4b60627..6e0e9f6 100644 --- a/modules/entt/test/entt/entity/storage.cpp +++ b/modules/entt/test/entt/entity/storage.cpp @@ -1,697 +1,1854 @@ -#include -#include +#include +#include #include -#include +#include +#include #include #include +#include #include +#include #include +#include "../common/throwing_allocator.hpp" +#include "../common/throwing_type.hpp" +#include "../common/tracked_memory_resource.hpp" + +struct empty_stable_type { + static constexpr auto in_place_delete = true; +}; + +struct boxed_int { + int value; +}; + +struct stable_type { + static constexpr auto in_place_delete = true; + int value; +}; + +struct non_default_constructible { + non_default_constructible() = delete; + + non_default_constructible(int v) + : value{v} {} + + int value; +}; + +struct update_from_destructor { + update_from_destructor(entt::storage &ref, entt::entity other) + : storage{&ref}, + target{other} {} -struct empty_type {}; -struct boxed_int { int value; }; + update_from_destructor(update_from_destructor &&other) ENTT_NOEXCEPT + : storage{std::exchange(other.storage, nullptr)}, + target{std::exchange(other.target, entt::null)} {} -struct throwing_component { - struct constructor_exception: std::exception {}; + update_from_destructor &operator=(update_from_destructor &&other) ENTT_NOEXCEPT { + storage = std::exchange(other.storage, nullptr); + target = std::exchange(other.target, entt::null); + return *this; + } - [[noreturn]] throwing_component() { throw constructor_exception{}; } + ~update_from_destructor() { + if(target != entt::null && storage->contains(target)) { + storage->erase(target); + } + } + +private: + entt::storage *storage{}; + entt::entity target{entt::null}; +}; + +struct create_from_constructor { + create_from_constructor(entt::storage &ref, entt::entity other) + : child{other} { + if(child != entt::null) { + ref.emplace(child, ref, entt::null); + } + } + + create_from_constructor(create_from_constructor &&other) ENTT_NOEXCEPT = default; + create_from_constructor &operator=(create_from_constructor &&other) ENTT_NOEXCEPT = default; + + entt::entity child; +}; - // necessary to avoid the short-circuit construct() logic for empty objects - int data; +template<> +struct entt::component_traits> { + static constexpr auto in_place_delete = true; + static constexpr auto page_size = ENTT_PACKED_PAGE; }; +bool operator==(const boxed_int &lhs, const boxed_int &rhs) { + return lhs.value == rhs.value; +} + TEST(Storage, Functionalities) { - entt::storage set; + entt::storage pool; + + ASSERT_NO_THROW([[maybe_unused]] auto alloc = pool.get_allocator()); + ASSERT_EQ(pool.type(), entt::type_id()); + + pool.reserve(42); + + ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE); + ASSERT_TRUE(pool.empty()); + ASSERT_EQ(pool.size(), 0u); + ASSERT_EQ(std::as_const(pool).begin(), std::as_const(pool).end()); + ASSERT_EQ(pool.begin(), pool.end()); + ASSERT_FALSE(pool.contains(entt::entity{0})); + ASSERT_FALSE(pool.contains(entt::entity{41})); + + pool.reserve(0); + + ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE); + ASSERT_TRUE(pool.empty()); + + pool.emplace(entt::entity{41}, 3); + + ASSERT_FALSE(pool.empty()); + ASSERT_EQ(pool.size(), 1u); + ASSERT_NE(std::as_const(pool).begin(), std::as_const(pool).end()); + ASSERT_NE(pool.begin(), pool.end()); + ASSERT_FALSE(pool.contains(entt::entity{0})); + ASSERT_TRUE(pool.contains(entt::entity{41})); + + ASSERT_EQ(pool.get(entt::entity{41}), 3); + ASSERT_EQ(std::as_const(pool).get(entt::entity{41}), 3); + ASSERT_EQ(pool.get_as_tuple(entt::entity{41}), std::make_tuple(3)); + ASSERT_EQ(std::as_const(pool).get_as_tuple(entt::entity{41}), std::make_tuple(3)); + + pool.erase(entt::entity{41}); + + ASSERT_TRUE(pool.empty()); + ASSERT_EQ(pool.size(), 0u); + ASSERT_EQ(std::as_const(pool).begin(), std::as_const(pool).end()); + ASSERT_EQ(pool.begin(), pool.end()); + ASSERT_FALSE(pool.contains(entt::entity{0})); + ASSERT_FALSE(pool.contains(entt::entity{41})); + + pool.emplace(entt::entity{41}, 12); + + ASSERT_EQ(pool.get(entt::entity{41}), 12); + ASSERT_EQ(std::as_const(pool).get(entt::entity{41}), 12); + ASSERT_EQ(pool.get_as_tuple(entt::entity{41}), std::make_tuple(12)); + ASSERT_EQ(std::as_const(pool).get_as_tuple(entt::entity{41}), std::make_tuple(12)); + + pool.clear(); + + ASSERT_TRUE(pool.empty()); + ASSERT_EQ(pool.size(), 0u); + ASSERT_EQ(std::as_const(pool).begin(), std::as_const(pool).end()); + ASSERT_EQ(pool.begin(), pool.end()); + ASSERT_FALSE(pool.contains(entt::entity{0})); + ASSERT_FALSE(pool.contains(entt::entity{41})); + + ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE); + + pool.shrink_to_fit(); + + ASSERT_EQ(pool.capacity(), 0u); +} + +TEST(Storage, Move) { + entt::storage pool; + pool.emplace(entt::entity{3}, 3); + + ASSERT_TRUE(std::is_move_constructible_v); + ASSERT_TRUE(std::is_move_assignable_v); + ASSERT_EQ(pool.type(), entt::type_id()); + + entt::storage other{std::move(pool)}; + + ASSERT_TRUE(pool.empty()); + ASSERT_FALSE(other.empty()); + ASSERT_EQ(other.type(), entt::type_id()); + ASSERT_EQ(pool.at(0u), static_cast(entt::null)); + ASSERT_EQ(other.at(0u), entt::entity{3}); + ASSERT_EQ(other.get(entt::entity{3}), 3); + + pool = std::move(other); - set.reserve(42); + ASSERT_FALSE(pool.empty()); + ASSERT_TRUE(other.empty()); + ASSERT_EQ(pool.at(0u), entt::entity{3}); + ASSERT_EQ(pool.get(entt::entity{3}), 3); + ASSERT_EQ(other.at(0u), static_cast(entt::null)); - ASSERT_EQ(set.capacity(), 42); - ASSERT_TRUE(set.empty()); - ASSERT_EQ(set.size(), 0u); - ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end()); - ASSERT_EQ(set.begin(), set.end()); - ASSERT_FALSE(set.has(0)); - ASSERT_FALSE(set.has(41)); + other = entt::storage{}; + other.emplace(entt::entity{42}, 42); + other = std::move(pool); - set.construct(41, 3); + ASSERT_TRUE(pool.empty()); + ASSERT_FALSE(other.empty()); + ASSERT_EQ(pool.at(0u), static_cast(entt::null)); + ASSERT_EQ(other.at(0u), entt::entity{3}); + ASSERT_EQ(other.get(entt::entity{3}), 3); +} + +TEST(Storage, Swap) { + entt::storage pool; + entt::storage other; + + pool.emplace(entt::entity{42}, 41); + + other.emplace(entt::entity{9}, 8); + other.emplace(entt::entity{3}, 2); + other.erase(entt::entity{9}); - ASSERT_FALSE(set.empty()); - ASSERT_EQ(set.size(), 1u); - ASSERT_NE(std::as_const(set).begin(), std::as_const(set).end()); - ASSERT_NE(set.begin(), set.end()); - ASSERT_FALSE(set.has(0)); - ASSERT_TRUE(set.has(41)); - ASSERT_EQ(set.get(41), 3); - ASSERT_EQ(*set.try_get(41), 3); - ASSERT_EQ(set.try_get(99), nullptr); + ASSERT_EQ(pool.size(), 1u); + ASSERT_EQ(other.size(), 1u); - set.destroy(41); + pool.swap(other); - ASSERT_TRUE(set.empty()); - ASSERT_EQ(set.size(), 0u); - ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end()); - ASSERT_EQ(set.begin(), set.end()); - ASSERT_FALSE(set.has(0)); - ASSERT_FALSE(set.has(41)); + ASSERT_EQ(pool.type(), entt::type_id()); + ASSERT_EQ(other.type(), entt::type_id()); - set.construct(41, 12); + ASSERT_EQ(pool.size(), 1u); + ASSERT_EQ(other.size(), 1u); + + ASSERT_EQ(pool.at(0u), entt::entity{3}); + ASSERT_EQ(pool.get(entt::entity{3}), 2); + + ASSERT_EQ(other.at(0u), entt::entity{42}); + ASSERT_EQ(other.get(entt::entity{42}), 41); +} - ASSERT_EQ(set.get(41), 12); - ASSERT_EQ(*set.try_get(41), 12); - ASSERT_EQ(set.try_get(99), nullptr); +TEST(Storage, StableSwap) { + entt::storage pool; + entt::storage other; - set.reset(); + pool.emplace(entt::entity{42}, 41); - ASSERT_TRUE(set.empty()); - ASSERT_EQ(set.size(), 0u); - ASSERT_EQ(std::as_const(set).begin(), std::as_const(set).end()); - ASSERT_EQ(set.begin(), set.end()); - ASSERT_FALSE(set.has(0)); - ASSERT_FALSE(set.has(41)); + other.emplace(entt::entity{9}, 8); + other.emplace(entt::entity{3}, 2); + other.erase(entt::entity{9}); - ASSERT_EQ(set.capacity(), 42); + ASSERT_EQ(pool.size(), 1u); + ASSERT_EQ(other.size(), 2u); - set.shrink_to_fit(); + pool.swap(other); - ASSERT_EQ(set.capacity(), 0); + ASSERT_EQ(pool.type(), entt::type_id()); + ASSERT_EQ(other.type(), entt::type_id()); - (void)entt::storage{std::move(set)}; - entt::storage other; - other = std::move(set); + ASSERT_EQ(pool.size(), 2u); + ASSERT_EQ(other.size(), 1u); + + ASSERT_EQ(pool.at(1u), entt::entity{3}); + ASSERT_EQ(pool.get(entt::entity{3}).value, 2); + + ASSERT_EQ(other.at(0u), entt::entity{42}); + ASSERT_EQ(other.get(entt::entity{42}).value, 41); } TEST(Storage, EmptyType) { - entt::storage set; + entt::storage pool; + pool.emplace(entt::entity{99}); + + ASSERT_NO_THROW([[maybe_unused]] auto alloc = pool.get_allocator()); + ASSERT_EQ(pool.type(), entt::type_id()); + ASSERT_TRUE(pool.contains(entt::entity{99})); + + entt::storage other{std::move(pool)}; + + ASSERT_FALSE(pool.contains(entt::entity{99})); + ASSERT_TRUE(other.contains(entt::entity{99})); + + pool = std::move(other); + + ASSERT_TRUE(pool.contains(entt::entity{99})); + ASSERT_FALSE(other.contains(entt::entity{99})); +} + +TEST(Storage, Insert) { + entt::storage pool; + entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; + pool.insert(std::begin(entities), std::end(entities), stable_type{99}); + + ASSERT_TRUE(pool.contains(entities[0u])); + ASSERT_TRUE(pool.contains(entities[1u])); + + ASSERT_FALSE(pool.empty()); + ASSERT_EQ(pool.size(), 2u); + ASSERT_EQ(pool.get(entities[0u]).value, 99); + ASSERT_EQ(pool.get(entities[1u]).value, 99); + + pool.erase(std::begin(entities), std::end(entities)); + const stable_type values[2u] = {stable_type{42}, stable_type{3}}; + pool.insert(std::rbegin(entities), std::rend(entities), std::begin(values)); + + ASSERT_EQ(pool.size(), 4u); + ASSERT_EQ(pool.at(2u), entities[1u]); + ASSERT_EQ(pool.at(3u), entities[0u]); + ASSERT_EQ(pool.index(entities[0u]), 3u); + ASSERT_EQ(pool.index(entities[1u]), 2u); + ASSERT_EQ(pool.get(entities[0u]).value, 3); + ASSERT_EQ(pool.get(entities[1u]).value, 42); +} + +TEST(Storage, InsertEmptyType) { + entt::storage pool; + entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; + + pool.insert(std::begin(entities), std::end(entities)); + + ASSERT_TRUE(pool.contains(entities[0u])); + ASSERT_TRUE(pool.contains(entities[1u])); + + ASSERT_FALSE(pool.empty()); + ASSERT_EQ(pool.size(), 2u); + + pool.erase(std::begin(entities), std::end(entities)); + const empty_stable_type values[2u]{}; + pool.insert(std::rbegin(entities), std::rend(entities), std::begin(values)); + + ASSERT_EQ(pool.size(), 4u); + ASSERT_EQ(pool.at(2u), entities[1u]); + ASSERT_EQ(pool.at(3u), entities[0u]); + ASSERT_EQ(pool.index(entities[0u]), 3u); + ASSERT_EQ(pool.index(entities[1u]), 2u); +} + +TEST(Storage, Erase) { + entt::storage pool; + entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}}; + + pool.emplace(entities[0u]); + pool.emplace(entities[1u]); + pool.emplace(entities[2u]); + pool.erase(std::begin(entities), std::end(entities)); + + ASSERT_TRUE(pool.empty()); + + pool.emplace(entities[0u], 0); + pool.emplace(entities[1u], 1); + pool.emplace(entities[2u], 2); + pool.erase(entities, entities + 2u); + + ASSERT_FALSE(pool.empty()); + ASSERT_EQ(*pool.begin(), 2); + + pool.erase(entities[2u]); + + ASSERT_TRUE(pool.empty()); + + pool.emplace(entities[0u], 0); + pool.emplace(entities[1u], 1); + pool.emplace(entities[2u], 2); + std::swap(entities[1u], entities[2u]); + pool.erase(entities, entities + 2u); + + ASSERT_FALSE(pool.empty()); + ASSERT_EQ(*pool.begin(), 1); +} + +TEST(Storage, CrossErase) { + entt::sparse_set set; + entt::storage pool; + entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; + + pool.emplace(entities[0u], 3); + pool.emplace(entities[1u], 42); + set.emplace(entities[1u]); + pool.erase(set.begin(), set.end()); + + ASSERT_TRUE(pool.contains(entities[0u])); + ASSERT_FALSE(pool.contains(entities[1u])); + ASSERT_EQ(pool.raw()[0u][0u], 3); +} + +TEST(Storage, StableErase) { + entt::storage pool; + entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}}; + + pool.emplace(entities[0u], stable_type{0}); + pool.emplace(entities[1u], stable_type{1}); + pool.emplace(entities[2u], stable_type{2}); + + pool.erase(std::begin(entities), std::end(entities)); + + ASSERT_FALSE(pool.empty()); + ASSERT_EQ(pool.size(), 3u); + ASSERT_TRUE(pool.at(2u) == entt::tombstone); + + pool.emplace(entities[2u], stable_type{2}); + pool.emplace(entities[0u], stable_type{0}); + pool.emplace(entities[1u], stable_type{1}); + + ASSERT_EQ(pool.get(entities[0u]).value, 0); + ASSERT_EQ(pool.get(entities[1u]).value, 1); + ASSERT_EQ(pool.get(entities[2u]).value, 2); + + ASSERT_EQ(pool.begin()->value, 2); + ASSERT_EQ(pool.index(entities[0u]), 1u); + ASSERT_EQ(pool.index(entities[1u]), 0u); + ASSERT_EQ(pool.index(entities[2u]), 2u); + + pool.erase(entities, entities + 2u); + + ASSERT_FALSE(pool.empty()); + ASSERT_EQ(pool.size(), 3u); + ASSERT_EQ(pool.begin()->value, 2); + ASSERT_EQ(pool.index(entities[2u]), 2u); + + pool.erase(entities[2u]); + + ASSERT_FALSE(pool.empty()); + ASSERT_EQ(pool.size(), 3u); + ASSERT_FALSE(pool.contains(entities[0u])); + ASSERT_FALSE(pool.contains(entities[1u])); + ASSERT_FALSE(pool.contains(entities[2u])); + + pool.emplace(entities[0u], stable_type{0}); + pool.emplace(entities[1u], stable_type{1}); + pool.emplace(entities[2u], stable_type{2}); + std::swap(entities[1u], entities[2u]); + pool.erase(entities, entities + 2u); + + ASSERT_FALSE(pool.empty()); + ASSERT_EQ(pool.size(), 3u); + ASSERT_TRUE(pool.contains(entities[2u])); + ASSERT_EQ(pool.index(entities[2u]), 0u); + ASSERT_EQ(pool.get(entities[2u]).value, 1); - set.construct(42); - set.construct(99); + pool.compact(); - ASSERT_TRUE(set.has(42)); - ASSERT_TRUE(set.has(99)); + ASSERT_FALSE(pool.empty()); + ASSERT_EQ(pool.size(), 1u); + ASSERT_EQ(pool.begin()->value, 1); - auto &&component = set.get(42); + pool.clear(); - ASSERT_TRUE((std::is_same_v)); + ASSERT_EQ(pool.size(), 0u); + + pool.emplace(entities[0u], stable_type{0}); + pool.emplace(entities[1u], stable_type{2}); + pool.emplace(entities[2u], stable_type{1}); + pool.erase(entities[2u]); + + pool.erase(entities[0u]); + pool.erase(entities[1u]); + + ASSERT_EQ(pool.size(), 3u); + ASSERT_TRUE(pool.at(2u) == entt::tombstone); + + pool.emplace(entities[0u], stable_type{99}); + + ASSERT_EQ((++pool.begin())->value, 99); + + pool.emplace(entities[1u], stable_type{2}); + pool.emplace(entities[2u], stable_type{1}); + pool.emplace(entt::entity{0}, stable_type{7}); + + ASSERT_EQ(pool.size(), 4u); + ASSERT_EQ(pool.begin()->value, 7); + ASSERT_EQ(pool.at(0u), entities[1u]); + ASSERT_EQ(pool.at(1u), entities[0u]); + ASSERT_EQ(pool.at(2u), entities[2u]); + + ASSERT_EQ(pool.get(entities[0u]).value, 99); + ASSERT_EQ(pool.get(entities[1u]).value, 2); + ASSERT_EQ(pool.get(entities[2u]).value, 1); +} + +TEST(Storage, CrossStableErase) { + entt::sparse_set set; + entt::storage pool; + entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; + + pool.emplace(entities[0u], 3); + pool.emplace(entities[1u], 42); + set.emplace(entities[1u]); + pool.erase(set.begin(), set.end()); + + ASSERT_TRUE(pool.contains(entities[0u])); + ASSERT_FALSE(pool.contains(entities[1u])); + ASSERT_EQ(pool.raw()[0u][0u].value, 3); +} + +TEST(Storage, Remove) { + entt::storage pool; + entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}}; + + pool.emplace(entities[0u]); + pool.emplace(entities[1u]); + pool.emplace(entities[2u]); + + ASSERT_EQ(pool.remove(std::begin(entities), std::end(entities)), 3u); + ASSERT_EQ(pool.remove(std::begin(entities), std::end(entities)), 0u); + ASSERT_TRUE(pool.empty()); + + pool.emplace(entities[0u], 0); + pool.emplace(entities[1u], 1); + pool.emplace(entities[2u], 2); + + ASSERT_EQ(pool.remove(entities, entities + 2u), 2u); + ASSERT_FALSE(pool.empty()); + ASSERT_EQ(*pool.begin(), 2); + + ASSERT_EQ(pool.remove(entities[2u]), 1u); + ASSERT_EQ(pool.remove(entities[2u]), 0u); + ASSERT_TRUE(pool.empty()); + + pool.emplace(entities[0u], 0); + pool.emplace(entities[1u], 1); + pool.emplace(entities[2u], 2); + std::swap(entities[1u], entities[2u]); + + ASSERT_EQ(pool.remove(entities, entities + 2u), 2u); + ASSERT_FALSE(pool.empty()); + ASSERT_EQ(*pool.begin(), 1); +} + +TEST(Storage, CrossRemove) { + entt::sparse_set set; + entt::storage pool; + entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; + + pool.emplace(entities[0u], 3); + pool.emplace(entities[1u], 42); + set.emplace(entities[1u]); + pool.remove(set.begin(), set.end()); + + ASSERT_TRUE(pool.contains(entities[0u])); + ASSERT_FALSE(pool.contains(entities[1u])); + ASSERT_EQ(pool.raw()[0u][0u], 3); +} + +TEST(Storage, StableRemove) { + entt::storage pool; + entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}}; + + pool.emplace(entities[0u], stable_type{0}); + pool.emplace(entities[1u], stable_type{1}); + pool.emplace(entities[2u], stable_type{2}); + + ASSERT_EQ(pool.remove(std::begin(entities), std::end(entities)), 3u); + ASSERT_EQ(pool.remove(std::begin(entities), std::end(entities)), 0u); + ASSERT_FALSE(pool.empty()); + ASSERT_EQ(pool.size(), 3u); + ASSERT_TRUE(pool.at(2u) == entt::tombstone); + + pool.emplace(entities[2u], stable_type{2}); + pool.emplace(entities[0u], stable_type{0}); + pool.emplace(entities[1u], stable_type{1}); + + ASSERT_EQ(pool.get(entities[0u]).value, 0); + ASSERT_EQ(pool.get(entities[1u]).value, 1); + ASSERT_EQ(pool.get(entities[2u]).value, 2); + + ASSERT_EQ(pool.begin()->value, 2); + ASSERT_EQ(pool.index(entities[0u]), 1u); + ASSERT_EQ(pool.index(entities[1u]), 0u); + ASSERT_EQ(pool.index(entities[2u]), 2u); + + ASSERT_EQ(pool.remove(entities, entities + 2u), 2u); + ASSERT_FALSE(pool.empty()); + ASSERT_EQ(pool.size(), 3u); + ASSERT_EQ(pool.begin()->value, 2); + ASSERT_EQ(pool.index(entities[2u]), 2u); + + ASSERT_EQ(pool.remove(entities[2u]), 1u); + ASSERT_EQ(pool.remove(entities[2u]), 0u); + ASSERT_EQ(pool.remove(entities[2u]), 0u); + ASSERT_FALSE(pool.empty()); + ASSERT_EQ(pool.size(), 3u); + ASSERT_FALSE(pool.contains(entities[0u])); + ASSERT_FALSE(pool.contains(entities[1u])); + ASSERT_FALSE(pool.contains(entities[2u])); + + pool.emplace(entities[0u], stable_type{0}); + pool.emplace(entities[1u], stable_type{1}); + pool.emplace(entities[2u], stable_type{2}); + std::swap(entities[1u], entities[2u]); + + ASSERT_EQ(pool.remove(entities, entities + 2u), 2u); + ASSERT_FALSE(pool.empty()); + ASSERT_EQ(pool.size(), 3u); + ASSERT_TRUE(pool.contains(entities[2u])); + ASSERT_EQ(pool.index(entities[2u]), 0u); + ASSERT_EQ(pool.get(entities[2u]).value, 1); + + pool.compact(); + + ASSERT_FALSE(pool.empty()); + ASSERT_EQ(pool.size(), 1u); + ASSERT_EQ(pool.begin()->value, 1); + + pool.clear(); + + ASSERT_EQ(pool.size(), 0u); + + pool.emplace(entities[0u], stable_type{0}); + pool.emplace(entities[1u], stable_type{2}); + pool.emplace(entities[2u], stable_type{1}); + + ASSERT_EQ(pool.remove(entities[2u]), 1u); + ASSERT_EQ(pool.remove(entities[2u]), 0u); + + ASSERT_EQ(pool.remove(entities[0u]), 1u); + ASSERT_EQ(pool.remove(entities[1u]), 1u); + ASSERT_EQ(pool.remove(entities, entities + 2u), 0u); + + ASSERT_EQ(pool.size(), 3u); + ASSERT_TRUE(pool.at(2u) == entt::tombstone); + + pool.emplace(entities[0u], stable_type{99}); + + ASSERT_EQ((++pool.begin())->value, 99); + + pool.emplace(entities[1u], stable_type{2}); + pool.emplace(entities[2u], stable_type{1}); + pool.emplace(entt::entity{0}, stable_type{7}); + + ASSERT_EQ(pool.size(), 4u); + ASSERT_EQ(pool.begin()->value, 7); + ASSERT_EQ(pool.at(0u), entities[1u]); + ASSERT_EQ(pool.at(1u), entities[0u]); + ASSERT_EQ(pool.at(2u), entities[2u]); + + ASSERT_EQ(pool.get(entities[0u]).value, 99); + ASSERT_EQ(pool.get(entities[1u]).value, 2); + ASSERT_EQ(pool.get(entities[2u]).value, 1); +} + +TEST(Storage, CrossStableRemove) { + entt::sparse_set set; + entt::storage pool; + entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; + + pool.emplace(entities[0u], 3); + pool.emplace(entities[1u], 42); + set.emplace(entities[1u]); + pool.remove(set.begin(), set.end()); + + ASSERT_TRUE(pool.contains(entities[0u])); + ASSERT_FALSE(pool.contains(entities[1u])); + ASSERT_EQ(pool.raw()[0u][0u].value, 3); +} + +TEST(Storage, TypeFromBase) { + entt::storage pool; + entt::sparse_set &base = pool; + entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; + + ASSERT_EQ(pool.type(), entt::type_id()); + ASSERT_EQ(pool.type(), base.type()); + + ASSERT_FALSE(pool.contains(entities[0u])); + ASSERT_FALSE(pool.contains(entities[1u])); + + int instance = 42; + + ASSERT_NE(base.emplace(entities[0u], &instance), base.end()); + + ASSERT_TRUE(pool.contains(entities[0u])); + ASSERT_FALSE(pool.contains(entities[1u])); + ASSERT_EQ(base.get(entities[0u]), &pool.get(entities[0u])); + ASSERT_EQ(pool.get(entities[0u]), 42); + + base.erase(entities[0u]); + + ASSERT_NE(base.insert(std::begin(entities), std::end(entities)), base.end()); + + ASSERT_TRUE(pool.contains(entities[0u])); + ASSERT_TRUE(pool.contains(entities[1u])); + ASSERT_EQ(pool.get(entities[0u]), 0); + ASSERT_EQ(pool.get(entities[1u]), 0); + + base.erase(std::begin(entities), std::end(entities)); + + ASSERT_TRUE(pool.empty()); +} + +TEST(Storage, EmptyTypeFromBase) { + entt::storage pool; + entt::sparse_set &base = pool; + entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; + + ASSERT_EQ(pool.type(), entt::type_id()); + ASSERT_EQ(pool.type(), base.type()); + + ASSERT_FALSE(pool.contains(entities[0u])); + ASSERT_FALSE(pool.contains(entities[1u])); + + empty_stable_type instance{}; + + ASSERT_NE(base.emplace(entities[0u], &instance), base.end()); + + ASSERT_EQ(pool.size(), 1u); + ASSERT_TRUE(pool.contains(entities[0u])); + ASSERT_FALSE(pool.contains(entities[1u])); + ASSERT_EQ(base.get(entities[0u]), nullptr); + ASSERT_EQ(base.index(entities[0u]), 0u); + + base.erase(entities[0u]); + + ASSERT_NE(base.insert(std::begin(entities), std::end(entities)), base.end()); + + ASSERT_EQ(pool.size(), 3u); + ASSERT_TRUE(pool.contains(entities[0u])); + ASSERT_TRUE(pool.contains(entities[1u])); + ASSERT_EQ(base.index(entities[0u]), 1u); + ASSERT_EQ(base.index(entities[1u]), 2u); + + base.erase(std::begin(entities), std::end(entities)); + + ASSERT_NE(base.insert(std::rbegin(entities), std::rend(entities)), base.end()); + + ASSERT_EQ(pool.size(), 5u); + ASSERT_TRUE(pool.contains(entities[0u])); + ASSERT_TRUE(pool.contains(entities[1u])); + ASSERT_EQ(base.index(entities[0u]), 4u); + ASSERT_EQ(base.index(entities[1u]), 3u); + + base.erase(std::begin(entities), std::end(entities)); + + ASSERT_FALSE(pool.empty()); + ASSERT_EQ(pool.size(), 5u); + + for(std::size_t pos{}, last = base.size(); pos != last; ++pos) { + ASSERT_TRUE(base[pos] == entt::tombstone); + } } -TEST(Storage, BatchAdd) { - entt::storage set; - entt::storage::entity_type entities[2]; +TEST(Storage, NonDefaultConstructibleTypeFromBase) { + entt::storage pool; + entt::sparse_set &base = pool; + entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; + + ASSERT_EQ(pool.type(), entt::type_id()); + ASSERT_EQ(pool.type(), base.type()); + + ASSERT_FALSE(pool.contains(entities[0u])); + ASSERT_FALSE(pool.contains(entities[1u])); + + ASSERT_EQ(base.emplace(entities[0u]), base.end()); + + ASSERT_FALSE(pool.contains(entities[0u])); + ASSERT_FALSE(pool.contains(entities[1u])); + ASSERT_EQ(base.find(entities[0u]), base.end()); + ASSERT_TRUE(pool.empty()); - entities[0] = 3; - entities[1] = 42; + non_default_constructible instance{3}; - set.reserve(4); - set.construct(12, 21); - auto *component = set.batch(std::begin(entities), std::end(entities)); - set.construct(24, 42); + ASSERT_NE(base.emplace(entities[0u], &instance), base.end()); - ASSERT_TRUE(set.has(entities[0])); - ASSERT_TRUE(set.has(entities[1])); - ASSERT_FALSE(set.has(0)); - ASSERT_FALSE(set.has(9)); - ASSERT_TRUE(set.has(12)); - ASSERT_TRUE(set.has(24)); + ASSERT_TRUE(pool.contains(entities[0u])); + ASSERT_FALSE(pool.contains(entities[1u])); - ASSERT_FALSE(set.empty()); - ASSERT_EQ(set.size(), 4u); - ASSERT_EQ(set.get(12), 21); - ASSERT_EQ(set.get(entities[0]), 0); - ASSERT_EQ(set.get(entities[1]), 0); - ASSERT_EQ(set.get(24), 42); + base.erase(entities[0u]); - component[0] = 1; - component[1] = 2; + ASSERT_TRUE(pool.empty()); + ASSERT_FALSE(pool.contains(entities[0u])); - ASSERT_EQ(set.get(entities[0]), 1); - ASSERT_EQ(set.get(entities[1]), 2); + ASSERT_EQ(base.insert(std::begin(entities), std::end(entities)), base.end()); + + ASSERT_FALSE(pool.contains(entities[0u])); + ASSERT_FALSE(pool.contains(entities[1u])); + ASSERT_EQ(base.find(entities[0u]), base.end()); + ASSERT_EQ(base.find(entities[1u]), base.end()); + ASSERT_TRUE(pool.empty()); } -TEST(Storage, BatchAddEmptyType) { - entt::storage set; - entt::storage::entity_type entities[2]; +TEST(Storage, NonCopyConstructibleTypeFromBase) { + entt::storage> pool; + entt::sparse_set &base = pool; + entt::entity entities[2u]{entt::entity{3}, entt::entity{42}}; + + ASSERT_EQ(pool.type(), entt::type_id>()); + ASSERT_EQ(pool.type(), base.type()); + + ASSERT_FALSE(pool.contains(entities[0u])); + ASSERT_FALSE(pool.contains(entities[1u])); + + ASSERT_NE(base.emplace(entities[0u]), base.end()); - entities[0] = 3; - entities[1] = 42; + ASSERT_TRUE(pool.contains(entities[0u])); + ASSERT_FALSE(pool.contains(entities[1u])); + ASSERT_NE(base.find(entities[0u]), base.end()); + ASSERT_FALSE(pool.empty()); - set.reserve(4); - set.construct(12); - set.batch(std::begin(entities), std::end(entities)); - set.construct(24); + std::unique_ptr instance = std::make_unique(3); - ASSERT_TRUE(set.has(entities[0])); - ASSERT_TRUE(set.has(entities[1])); - ASSERT_FALSE(set.has(0)); - ASSERT_FALSE(set.has(9)); - ASSERT_TRUE(set.has(12)); - ASSERT_TRUE(set.has(24)); + ASSERT_EQ(base.emplace(entities[1u], &instance), base.end()); - ASSERT_FALSE(set.empty()); - ASSERT_EQ(set.size(), 4u); + ASSERT_TRUE(pool.contains(entities[0u])); + ASSERT_FALSE(pool.contains(entities[1u])); - auto &&component = set.get(entities[0]); + base.erase(entities[0u]); - ASSERT_TRUE((std::is_same_v)); + ASSERT_TRUE(pool.empty()); + ASSERT_FALSE(pool.contains(entities[0u])); + + ASSERT_NE(base.insert(std::begin(entities), std::end(entities)), base.end()); + + ASSERT_TRUE(pool.contains(entities[0u])); + ASSERT_TRUE(pool.contains(entities[1u])); + ASSERT_NE(base.find(entities[0u]), base.end()); + ASSERT_NE(base.find(entities[1u]), base.end()); + ASSERT_FALSE(pool.empty()); +} + +TEST(Storage, Compact) { + entt::storage pool; + + ASSERT_TRUE(pool.empty()); + ASSERT_EQ(pool.size(), 0u); + + pool.compact(); + + ASSERT_TRUE(pool.empty()); + ASSERT_EQ(pool.size(), 0u); + + pool.emplace(entt::entity{0}, stable_type{0}); + pool.compact(); + + ASSERT_FALSE(pool.empty()); + ASSERT_EQ(pool.size(), 1u); + + pool.emplace(entt::entity{42}, stable_type{42}); + pool.erase(entt::entity{0}); + + ASSERT_EQ(pool.size(), 2u); + ASSERT_EQ(pool.index(entt::entity{42}), 1u); + ASSERT_EQ(pool.get(entt::entity{42}).value, 42); + + pool.compact(); + + ASSERT_EQ(pool.size(), 1u); + ASSERT_EQ(pool.index(entt::entity{42}), 0u); + ASSERT_EQ(pool.get(entt::entity{42}).value, 42); + + pool.emplace(entt::entity{0}, stable_type{0}); + pool.compact(); + + ASSERT_EQ(pool.size(), 2u); + ASSERT_EQ(pool.index(entt::entity{42}), 0u); + ASSERT_EQ(pool.index(entt::entity{0}), 1u); + ASSERT_EQ(pool.get(entt::entity{42}).value, 42); + ASSERT_EQ(pool.get(entt::entity{0}).value, 0); + + pool.erase(entt::entity{0}); + pool.erase(entt::entity{42}); + pool.compact(); + + ASSERT_TRUE(pool.empty()); +} + +TEST(Storage, ShrinkToFit) { + entt::storage pool; + + for(std::size_t next{}; next < ENTT_PACKED_PAGE; ++next) { + pool.emplace(entt::entity(next)); + } + + pool.emplace(entt::entity{ENTT_PACKED_PAGE}); + pool.erase(entt::entity{ENTT_PACKED_PAGE}); + + ASSERT_EQ(pool.capacity(), 2 * ENTT_PACKED_PAGE); + ASSERT_EQ(pool.size(), ENTT_PACKED_PAGE); + + pool.shrink_to_fit(); + + ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE); + ASSERT_EQ(pool.size(), ENTT_PACKED_PAGE); + + pool.clear(); + + ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE); + ASSERT_EQ(pool.size(), 0u); + + pool.shrink_to_fit(); + + ASSERT_EQ(pool.capacity(), 0u); + ASSERT_EQ(pool.size(), 0u); } TEST(Storage, AggregatesMustWork) { - struct aggregate_type { int value; }; + struct aggregate_type { + int value; + }; + // the goal of this test is to enforce the requirements for aggregate types - entt::storage{}.construct(0, 42); + entt::storage{}.emplace(entt::entity{0}, 42); +} + +TEST(Storage, SelfMoveSupport) { + // see #37 - this test shouldn't crash, that's all + entt::storage> pool; + entt::entity entity{}; + + ASSERT_EQ(pool.policy(), entt::deletion_policy::swap_and_pop); + + pool.emplace(entity).insert(42); + pool.erase(entity); + + ASSERT_FALSE(pool.contains(entity)); } -TEST(Storage, TypesFromStandardTemplateLibraryMustWork) { +TEST(Storage, SelfMoveSupportInPlaceDelete) { // see #37 - this test shouldn't crash, that's all - entt::storage> set; - set.construct(0).insert(42); - set.destroy(0); + entt::storage> pool; + entt::entity entity{}; + + ASSERT_EQ(pool.policy(), entt::deletion_policy::in_place); + + pool.emplace(entity).insert(42); + pool.erase(entity); + + ASSERT_FALSE(pool.contains(entity)); } TEST(Storage, Iterator) { - using iterator_type = typename entt::storage::iterator_type; + using iterator = typename entt::storage::iterator; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); - entt::storage set; - set.construct(3, 42); + entt::storage pool; + pool.emplace(entt::entity{3}, 42); - iterator_type end{set.begin()}; - iterator_type begin{}; - begin = set.end(); + iterator end{pool.begin()}; + iterator begin{}; + begin = pool.end(); std::swap(begin, end); - ASSERT_EQ(begin, set.begin()); - ASSERT_EQ(end, set.end()); + ASSERT_EQ(begin, pool.begin()); + ASSERT_EQ(end, pool.end()); ASSERT_NE(begin, end); - ASSERT_EQ(begin++, set.begin()); - ASSERT_EQ(begin--, set.end()); + ASSERT_EQ(begin.index(), 0); + ASSERT_EQ(end.index(), -1); + + ASSERT_EQ(begin++, pool.begin()); + ASSERT_EQ(begin--, pool.end()); - ASSERT_EQ(begin+1, set.end()); - ASSERT_EQ(end-1, set.begin()); + ASSERT_EQ(begin + 1, pool.end()); + ASSERT_EQ(end - 1, pool.begin()); - ASSERT_EQ(++begin, set.end()); - ASSERT_EQ(--begin, set.begin()); + ASSERT_EQ(++begin, pool.end()); + ASSERT_EQ(--begin, pool.begin()); - ASSERT_EQ(begin += 1, set.end()); - ASSERT_EQ(begin -= 1, set.begin()); + ASSERT_EQ(begin += 1, pool.end()); + ASSERT_EQ(begin -= 1, pool.begin()); - ASSERT_EQ(begin + (end - begin), set.end()); - ASSERT_EQ(begin - (begin - end), set.end()); + ASSERT_EQ(begin + (end - begin), pool.end()); + ASSERT_EQ(begin - (begin - end), pool.end()); - ASSERT_EQ(end - (end - begin), set.begin()); - ASSERT_EQ(end + (begin - end), set.begin()); + ASSERT_EQ(end - (end - begin), pool.begin()); + ASSERT_EQ(end + (begin - end), pool.begin()); - ASSERT_EQ(begin[0].value, set.begin()->value); + ASSERT_EQ(begin[0u].value, pool.begin()->value); ASSERT_LT(begin, end); - ASSERT_LE(begin, set.begin()); + ASSERT_LE(begin, pool.begin()); ASSERT_GT(end, begin); - ASSERT_GE(end, set.end()); + ASSERT_GE(end, pool.end()); + + ASSERT_EQ(begin.index(), 0); + ASSERT_EQ(end.index(), -1); + + pool.emplace(entt::entity{42}, 3); + begin = pool.begin(); + + ASSERT_EQ(begin.index(), 1); + ASSERT_EQ(end.index(), -1); + + ASSERT_EQ(begin[0u], boxed_int{3}); + ASSERT_EQ(begin[1u], boxed_int{42}); } TEST(Storage, ConstIterator) { - using iterator_type = typename entt::storage::const_iterator_type; + using iterator = typename entt::storage::const_iterator; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); - entt::storage set; - set.construct(3, 42); + entt::storage pool; + pool.emplace(entt::entity{3}, 42); - iterator_type cend{set.cbegin()}; - iterator_type cbegin{}; - cbegin = set.cend(); + iterator cend{pool.cbegin()}; + iterator cbegin{}; + cbegin = pool.cend(); std::swap(cbegin, cend); - ASSERT_EQ(cbegin, set.cbegin()); - ASSERT_EQ(cend, set.cend()); + ASSERT_EQ(cbegin, std::as_const(pool).begin()); + ASSERT_EQ(cend, std::as_const(pool).end()); + ASSERT_EQ(cbegin, pool.cbegin()); + ASSERT_EQ(cend, pool.cend()); ASSERT_NE(cbegin, cend); - ASSERT_EQ(cbegin++, set.cbegin()); - ASSERT_EQ(cbegin--, set.cend()); + ASSERT_EQ(cbegin.index(), 0); + ASSERT_EQ(cend.index(), -1); + + ASSERT_EQ(cbegin++, pool.cbegin()); + ASSERT_EQ(cbegin--, pool.cend()); - ASSERT_EQ(cbegin+1, set.cend()); - ASSERT_EQ(cend-1, set.cbegin()); + ASSERT_EQ(cbegin + 1, pool.cend()); + ASSERT_EQ(cend - 1, pool.cbegin()); - ASSERT_EQ(++cbegin, set.cend()); - ASSERT_EQ(--cbegin, set.cbegin()); + ASSERT_EQ(++cbegin, pool.cend()); + ASSERT_EQ(--cbegin, pool.cbegin()); - ASSERT_EQ(cbegin += 1, set.cend()); - ASSERT_EQ(cbegin -= 1, set.cbegin()); + ASSERT_EQ(cbegin += 1, pool.cend()); + ASSERT_EQ(cbegin -= 1, pool.cbegin()); - ASSERT_EQ(cbegin + (cend - cbegin), set.cend()); - ASSERT_EQ(cbegin - (cbegin - cend), set.cend()); + ASSERT_EQ(cbegin + (cend - cbegin), pool.cend()); + ASSERT_EQ(cbegin - (cbegin - cend), pool.cend()); - ASSERT_EQ(cend - (cend - cbegin), set.cbegin()); - ASSERT_EQ(cend + (cbegin - cend), set.cbegin()); + ASSERT_EQ(cend - (cend - cbegin), pool.cbegin()); + ASSERT_EQ(cend + (cbegin - cend), pool.cbegin()); - ASSERT_EQ(cbegin[0].value, set.cbegin()->value); + ASSERT_EQ(cbegin[0u].value, pool.cbegin()->value); ASSERT_LT(cbegin, cend); - ASSERT_LE(cbegin, set.cbegin()); + ASSERT_LE(cbegin, pool.cbegin()); ASSERT_GT(cend, cbegin); - ASSERT_GE(cend, set.cend()); + ASSERT_GE(cend, pool.cend()); + + ASSERT_EQ(cbegin.index(), 0); + ASSERT_EQ(cend.index(), -1); + + pool.emplace(entt::entity{42}, 3); + cbegin = pool.cbegin(); + + ASSERT_EQ(cbegin.index(), 1); + ASSERT_EQ(cend.index(), -1); + + ASSERT_EQ(cbegin[0u], boxed_int{3}); + ASSERT_EQ(cbegin[1u], boxed_int{42}); } -TEST(Storage, IteratorEmptyType) { - using iterator_type = typename entt::storage::iterator_type; - entt::storage set; - set.construct(3); +TEST(Storage, ReverseIterator) { + using reverse_iterator = typename entt::storage::reverse_iterator; - iterator_type end{set.begin()}; - iterator_type begin{}; - begin = set.end(); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + entt::storage pool; + pool.emplace(entt::entity{3}, 42); + + reverse_iterator end{pool.rbegin()}; + reverse_iterator begin{}; + begin = pool.rend(); std::swap(begin, end); - ASSERT_EQ(begin, set.begin()); - ASSERT_EQ(end, set.end()); + ASSERT_EQ(begin, pool.rbegin()); + ASSERT_EQ(end, pool.rend()); ASSERT_NE(begin, end); - ASSERT_EQ(begin++, set.begin()); - ASSERT_EQ(begin--, set.end()); + ASSERT_EQ(begin.base().index(), -1); + ASSERT_EQ(end.base().index(), 0); + + ASSERT_EQ(begin++, pool.rbegin()); + ASSERT_EQ(begin--, pool.rend()); - ASSERT_EQ(begin+1, set.end()); - ASSERT_EQ(end-1, set.begin()); + ASSERT_EQ(begin + 1, pool.rend()); + ASSERT_EQ(end - 1, pool.rbegin()); - ASSERT_EQ(++begin, set.end()); - ASSERT_EQ(--begin, set.begin()); + ASSERT_EQ(++begin, pool.rend()); + ASSERT_EQ(--begin, pool.rbegin()); - ASSERT_EQ(begin += 1, set.end()); - ASSERT_EQ(begin -= 1, set.begin()); + ASSERT_EQ(begin += 1, pool.rend()); + ASSERT_EQ(begin -= 1, pool.rbegin()); - ASSERT_EQ(begin + (end - begin), set.end()); - ASSERT_EQ(begin - (begin - end), set.end()); + ASSERT_EQ(begin + (end - begin), pool.rend()); + ASSERT_EQ(begin - (begin - end), pool.rend()); - ASSERT_EQ(end - (end - begin), set.begin()); - ASSERT_EQ(end + (begin - end), set.begin()); + ASSERT_EQ(end - (end - begin), pool.rbegin()); + ASSERT_EQ(end + (begin - end), pool.rbegin()); - ASSERT_EQ(set.begin().operator->(), nullptr); + ASSERT_EQ(begin[0u].value, pool.rbegin()->value); ASSERT_LT(begin, end); - ASSERT_LE(begin, set.begin()); + ASSERT_LE(begin, pool.rbegin()); ASSERT_GT(end, begin); - ASSERT_GE(end, set.end()); + ASSERT_GE(end, pool.rend()); + + ASSERT_EQ(begin.base().index(), -1); + ASSERT_EQ(end.base().index(), 0); + + pool.emplace(entt::entity{42}, 3); + end = pool.rend(); + + ASSERT_EQ(begin.base().index(), -1); + ASSERT_EQ(end.base().index(), 1); + + ASSERT_EQ(begin[0u], boxed_int{42}); + ASSERT_EQ(begin[1u], boxed_int{3}); +} + +TEST(Storage, ConstReverseIterator) { + using const_reverse_iterator = typename entt::storage::const_reverse_iterator; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + entt::storage pool; + pool.emplace(entt::entity{3}, 42); + + const_reverse_iterator cend{pool.crbegin()}; + const_reverse_iterator cbegin{}; + cbegin = pool.crend(); + std::swap(cbegin, cend); + + ASSERT_EQ(cbegin, std::as_const(pool).rbegin()); + ASSERT_EQ(cend, std::as_const(pool).rend()); + ASSERT_EQ(cbegin, pool.crbegin()); + ASSERT_EQ(cend, pool.crend()); + ASSERT_NE(cbegin, cend); + + ASSERT_EQ(cbegin.base().index(), -1); + ASSERT_EQ(cend.base().index(), 0); + + ASSERT_EQ(cbegin++, pool.crbegin()); + ASSERT_EQ(cbegin--, pool.crend()); + + ASSERT_EQ(cbegin + 1, pool.crend()); + ASSERT_EQ(cend - 1, pool.crbegin()); + + ASSERT_EQ(++cbegin, pool.crend()); + ASSERT_EQ(--cbegin, pool.crbegin()); + + ASSERT_EQ(cbegin += 1, pool.crend()); + ASSERT_EQ(cbegin -= 1, pool.crbegin()); + + ASSERT_EQ(cbegin + (cend - cbegin), pool.crend()); + ASSERT_EQ(cbegin - (cbegin - cend), pool.crend()); + + ASSERT_EQ(cend - (cend - cbegin), pool.crbegin()); + ASSERT_EQ(cend + (cbegin - cend), pool.crbegin()); + + ASSERT_EQ(cbegin[0u].value, pool.crbegin()->value); + + ASSERT_LT(cbegin, cend); + ASSERT_LE(cbegin, pool.crbegin()); + + ASSERT_GT(cend, cbegin); + ASSERT_GE(cend, pool.crend()); + + ASSERT_EQ(cbegin.base().index(), -1); + ASSERT_EQ(cend.base().index(), 0); + + pool.emplace(entt::entity{42}, 3); + cend = pool.crend(); + + ASSERT_EQ(cbegin.base().index(), -1); + ASSERT_EQ(cend.base().index(), 1); + + ASSERT_EQ(cbegin[0u], boxed_int{42}); + ASSERT_EQ(cbegin[1u], boxed_int{3}); +} + +TEST(Storage, IteratorConversion) { + entt::storage pool; + pool.emplace(entt::entity{3}, 42); + + typename entt::storage::iterator it = pool.begin(); + typename entt::storage::const_iterator cit = it; - set.construct(33); - auto &&component = *begin; + static_assert(std::is_same_v); + static_assert(std::is_same_v); - ASSERT_TRUE((std::is_same_v)); + ASSERT_EQ(it->value, 42); + ASSERT_EQ(it->value, cit->value); + + ASSERT_EQ(it - cit, 0); + ASSERT_EQ(cit - it, 0); + ASSERT_LE(it, cit); + ASSERT_LE(cit, it); + ASSERT_GE(it, cit); + ASSERT_GE(cit, it); + ASSERT_EQ(it, cit); + ASSERT_NE(++cit, it); +} + +TEST(Storage, Iterable) { + using iterator = typename entt::storage::iterable::iterator; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + static_assert(std::is_same_v>>); + static_assert(std::is_same_v); + + entt::storage pool; + pool.emplace(entt::entity{1}, 99); + pool.emplace(entt::entity{3}, 42); + auto iterable = pool.each(); + + iterator end{iterable.begin()}; + iterator begin{}; + begin = iterable.end(); + std::swap(begin, end); + + ASSERT_EQ(begin, iterable.begin()); + ASSERT_EQ(end, iterable.end()); + ASSERT_NE(begin, end); + + ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{3}); + ASSERT_EQ(std::get<1>(*begin.operator->().operator->()), boxed_int{42}); + ASSERT_EQ(std::get<0>(*begin), entt::entity{3}); + ASSERT_EQ(std::get<1>(*begin), boxed_int{42}); + + ASSERT_EQ(begin++, iterable.begin()); + ASSERT_EQ(++begin, iterable.end()); + + for(auto [entity, element]: iterable) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_TRUE(entity != entt::entity{1} || element == boxed_int{99}); + ASSERT_TRUE(entity != entt::entity{3} || element == boxed_int{42}); + } +} + +TEST(Storage, ConstIterable) { + using iterator = typename entt::storage::const_iterable::iterator; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + static_assert(std::is_same_v>>); + static_assert(std::is_same_v); + + entt::storage pool; + pool.emplace(entt::entity{1}, 99); + pool.emplace(entt::entity{3}, 42); + auto iterable = std::as_const(pool).each(); + + iterator end{iterable.cbegin()}; + iterator begin{}; + begin = iterable.cend(); + std::swap(begin, end); + + ASSERT_EQ(begin, iterable.cbegin()); + ASSERT_EQ(end, iterable.cend()); + ASSERT_NE(begin, end); + + ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{3}); + ASSERT_EQ(std::get<1>(*begin.operator->().operator->()), boxed_int{42}); + ASSERT_EQ(std::get<0>(*begin), entt::entity{3}); + ASSERT_EQ(std::get<1>(*begin), boxed_int{42}); + + ASSERT_EQ(begin++, iterable.cbegin()); + ASSERT_EQ(++begin, iterable.cend()); + + for(auto [entity, element]: iterable) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_TRUE(entity != entt::entity{1} || element == boxed_int{99}); + ASSERT_TRUE(entity != entt::entity{3} || element == boxed_int{42}); + } +} + +TEST(Storage, IterableIteratorConversion) { + entt::storage pool; + pool.emplace(entt::entity{3}, 42); + + typename entt::storage::iterable::iterator it = pool.each().begin(); + typename entt::storage::const_iterable::iterator cit = it; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + + ASSERT_EQ(it, cit); + ASSERT_NE(++cit, it); +} + +TEST(Storage, EmptyTypeIterable) { + using iterator = typename entt::storage::iterable::iterator; + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + static_assert(std::is_same_v>>); + static_assert(std::is_same_v); + + entt::storage pool; + pool.emplace(entt::entity{1}); + pool.emplace(entt::entity{3}); + auto iterable = pool.each(); + + iterator end{iterable.begin()}; + iterator begin{}; + begin = iterable.end(); + std::swap(begin, end); + + ASSERT_EQ(begin, iterable.begin()); + ASSERT_EQ(end, iterable.end()); + ASSERT_NE(begin, end); + + ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{3}); + ASSERT_EQ(std::get<0>(*begin), entt::entity{3}); + + ASSERT_EQ(begin++, iterable.begin()); + ASSERT_EQ(++begin, iterable.end()); + + for(auto [entity]: iterable) { + static_assert(std::is_same_v); + ASSERT_TRUE(entity == entt::entity{1} || entity == entt::entity{3}); + } +} + +TEST(Storage, IterableAlgorithmCompatibility) { + entt::storage pool; + pool.emplace(entt::entity{3}, 42); + + const auto iterable = pool.each(); + const auto it = std::find_if(iterable.begin(), iterable.end(), [](auto args) { return std::get<0>(args) == entt::entity{3}; }); + + ASSERT_EQ(std::get<0>(*it), entt::entity{3}); } TEST(Storage, Raw) { - entt::storage set; + entt::storage pool; - set.construct(3, 3); - set.construct(12, 6); - set.construct(42, 9); + pool.emplace(entt::entity{3}, 3); + pool.emplace(entt::entity{12}, 6); + pool.emplace(entt::entity{42}, 9); - ASSERT_EQ(set.get(3), 3); - ASSERT_EQ(std::as_const(set).get(12), 6); - ASSERT_EQ(set.get(42), 9); + ASSERT_EQ(pool.get(entt::entity{3}), 3); + ASSERT_EQ(std::as_const(pool).get(entt::entity{12}), 6); + ASSERT_EQ(pool.get(entt::entity{42}), 9); - ASSERT_EQ(*(set.raw() + 0u), 3); - ASSERT_EQ(*(std::as_const(set).raw() + 1u), 6); - ASSERT_EQ(*(set.raw() + 2u), 9); + ASSERT_EQ(pool.raw()[0u][0u], 3); + ASSERT_EQ(std::as_const(pool).raw()[0u][1u], 6); + ASSERT_EQ(pool.raw()[0u][2u], 9); } TEST(Storage, SortOrdered) { - entt::storage set; - - set.construct(12, boxed_int{12}); - set.construct(42, boxed_int{9}); - set.construct(7, boxed_int{6}); - set.construct(3, boxed_int{3}); - set.construct(9, boxed_int{1}); - - ASSERT_EQ(set.get(12).value, 12); - ASSERT_EQ(set.get(42).value, 9); - ASSERT_EQ(set.get(7).value, 6); - ASSERT_EQ(set.get(3).value, 3); - ASSERT_EQ(set.get(9).value, 1); - - set.sort([](auto lhs, auto rhs) { - return lhs.value < rhs.value; - }); - - ASSERT_EQ((set.raw() + 0u)->value, 12); - ASSERT_EQ((set.raw() + 1u)->value, 9); - ASSERT_EQ((set.raw() + 2u)->value, 6); - ASSERT_EQ((set.raw() + 3u)->value, 3); - ASSERT_EQ((set.raw() + 4u)->value, 1); - - auto begin = set.begin(); - auto end = set.end(); - - ASSERT_EQ((begin++)->value, 1); - ASSERT_EQ((begin++)->value, 3); - ASSERT_EQ((begin++)->value, 6); - ASSERT_EQ((begin++)->value, 9); - ASSERT_EQ((begin++)->value, 12); - ASSERT_EQ(begin, end); + entt::storage pool; + entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}}; + boxed_int values[5u]{{12}, {9}, {6}, {3}, {1}}; + + pool.insert(std::begin(entities), std::end(entities), values); + pool.sort([&pool](auto lhs, auto rhs) { return pool.get(lhs).value < pool.get(rhs).value; }); + + ASSERT_TRUE(std::equal(std::rbegin(entities), std::rend(entities), pool.entt::sparse_set::begin(), pool.entt::sparse_set::end())); + ASSERT_TRUE(std::equal(std::rbegin(values), std::rend(values), pool.begin(), pool.end())); } TEST(Storage, SortReverse) { - entt::storage set; - - set.construct(12, boxed_int{1}); - set.construct(42, boxed_int{3}); - set.construct(7, boxed_int{6}); - set.construct(3, boxed_int{9}); - set.construct(9, boxed_int{12}); - - ASSERT_EQ(set.get(12).value, 1); - ASSERT_EQ(set.get(42).value, 3); - ASSERT_EQ(set.get(7).value, 6); - ASSERT_EQ(set.get(3).value, 9); - ASSERT_EQ(set.get(9).value, 12); - - set.sort([&set](std::uint64_t lhs, std::uint64_t rhs) { - return set.get(lhs).value < set.get(rhs).value; - }); - - ASSERT_EQ((set.raw() + 0u)->value, 12); - ASSERT_EQ((set.raw() + 1u)->value, 9); - ASSERT_EQ((set.raw() + 2u)->value, 6); - ASSERT_EQ((set.raw() + 3u)->value, 3); - ASSERT_EQ((set.raw() + 4u)->value, 1); - - auto begin = set.begin(); - auto end = set.end(); - - ASSERT_EQ((begin++)->value, 1); - ASSERT_EQ((begin++)->value, 3); - ASSERT_EQ((begin++)->value, 6); - ASSERT_EQ((begin++)->value, 9); - ASSERT_EQ((begin++)->value, 12); - ASSERT_EQ(begin, end); + entt::storage pool; + entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}}; + boxed_int values[5u]{{1}, {3}, {6}, {9}, {12}}; + + pool.insert(std::begin(entities), std::end(entities), values); + pool.sort([&pool](auto lhs, auto rhs) { return pool.get(lhs).value < pool.get(rhs).value; }); + + ASSERT_TRUE(std::equal(std::begin(entities), std::end(entities), pool.entt::sparse_set::begin(), pool.entt::sparse_set::end())); + ASSERT_TRUE(std::equal(std::begin(values), std::end(values), pool.begin(), pool.end())); } TEST(Storage, SortUnordered) { - entt::storage set; - - set.construct(12, boxed_int{6}); - set.construct(42, boxed_int{3}); - set.construct(7, boxed_int{1}); - set.construct(3, boxed_int{9}); - set.construct(9, boxed_int{12}); - - ASSERT_EQ(set.get(12).value, 6); - ASSERT_EQ(set.get(42).value, 3); - ASSERT_EQ(set.get(7).value, 1); - ASSERT_EQ(set.get(3).value, 9); - ASSERT_EQ(set.get(9).value, 12); - - set.sort([](auto lhs, auto rhs) { - return lhs.value < rhs.value; - }); - - ASSERT_EQ((set.raw() + 0u)->value, 12); - ASSERT_EQ((set.raw() + 1u)->value, 9); - ASSERT_EQ((set.raw() + 2u)->value, 6); - ASSERT_EQ((set.raw() + 3u)->value, 3); - ASSERT_EQ((set.raw() + 4u)->value, 1); - - auto begin = set.begin(); - auto end = set.end(); - - ASSERT_EQ((begin++)->value, 1); - ASSERT_EQ((begin++)->value, 3); - ASSERT_EQ((begin++)->value, 6); - ASSERT_EQ((begin++)->value, 9); - ASSERT_EQ((begin++)->value, 12); + entt::storage pool; + entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}}; + boxed_int values[5u]{{6}, {3}, {1}, {9}, {12}}; + + pool.insert(std::begin(entities), std::end(entities), values); + pool.sort([&pool](auto lhs, auto rhs) { return pool.get(lhs).value < pool.get(rhs).value; }); + + auto begin = pool.begin(); + auto end = pool.end(); + + ASSERT_EQ(*(begin++), values[2u]); + ASSERT_EQ(*(begin++), values[1u]); + ASSERT_EQ(*(begin++), values[0u]); + ASSERT_EQ(*(begin++), values[3u]); + ASSERT_EQ(*(begin++), values[4u]); ASSERT_EQ(begin, end); + + ASSERT_EQ(pool.data()[0u], entities[4u]); + ASSERT_EQ(pool.data()[1u], entities[3u]); + ASSERT_EQ(pool.data()[2u], entities[0u]); + ASSERT_EQ(pool.data()[3u], entities[1u]); + ASSERT_EQ(pool.data()[4u], entities[2u]); } -TEST(Storage, RespectDisjoint) { - entt::storage lhs; - entt::storage rhs; +TEST(Storage, SortRange) { + entt::storage pool; + entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}}; + boxed_int values[5u]{{3}, {6}, {1}, {9}, {12}}; - lhs.construct(3, 3); - lhs.construct(12, 6); - lhs.construct(42, 9); + pool.insert(std::begin(entities), std::end(entities), values); + pool.sort_n(0u, [&pool](auto lhs, auto rhs) { return pool.get(lhs).value < pool.get(rhs).value; }); - ASSERT_EQ(std::as_const(lhs).get(3), 3); - ASSERT_EQ(std::as_const(lhs).get(12), 6); - ASSERT_EQ(std::as_const(lhs).get(42), 9); + ASSERT_TRUE(std::equal(std::rbegin(entities), std::rend(entities), pool.entt::sparse_set::begin(), pool.entt::sparse_set::end())); + ASSERT_TRUE(std::equal(std::rbegin(values), std::rend(values), pool.begin(), pool.end())); - lhs.respect(rhs); + pool.sort_n(2u, [&pool](auto lhs, auto rhs) { return pool.get(lhs).value < pool.get(rhs).value; }); - ASSERT_EQ(*(std::as_const(lhs).raw() + 0u), 3); - ASSERT_EQ(*(std::as_const(lhs).raw() + 1u), 6); - ASSERT_EQ(*(std::as_const(lhs).raw() + 2u), 9); + ASSERT_EQ(pool.raw()[0u][0u], values[1u]); + ASSERT_EQ(pool.raw()[0u][1u], values[0u]); + ASSERT_EQ(pool.raw()[0u][2u], values[2u]); - auto begin = lhs.begin(); - auto end = lhs.end(); + ASSERT_EQ(pool.data()[0u], entities[1u]); + ASSERT_EQ(pool.data()[1u], entities[0u]); + ASSERT_EQ(pool.data()[2u], entities[2u]); + + pool.sort_n(5u, [&pool](auto lhs, auto rhs) { return pool.get(lhs).value < pool.get(rhs).value; }); - ASSERT_EQ(*(begin++), 9); - ASSERT_EQ(*(begin++), 6); - ASSERT_EQ(*(begin++), 3); + auto begin = pool.begin(); + auto end = pool.end(); + + ASSERT_EQ(*(begin++), values[2u]); + ASSERT_EQ(*(begin++), values[0u]); + ASSERT_EQ(*(begin++), values[1u]); + ASSERT_EQ(*(begin++), values[3u]); + ASSERT_EQ(*(begin++), values[4u]); ASSERT_EQ(begin, end); + + ASSERT_EQ(pool.data()[0u], entities[4u]); + ASSERT_EQ(pool.data()[1u], entities[3u]); + ASSERT_EQ(pool.data()[2u], entities[1u]); + ASSERT_EQ(pool.data()[3u], entities[0u]); + ASSERT_EQ(pool.data()[4u], entities[2u]); } -TEST(Storage, RespectOverlap) { - entt::storage lhs; - entt::storage rhs; +TEST(Storage, RespectDisjoint) { + entt::storage lhs; + entt::storage rhs; - lhs.construct(3, 3); - lhs.construct(12, 6); - lhs.construct(42, 9); - rhs.construct(12, 6); + entt::entity lhs_entities[3u]{entt::entity{3}, entt::entity{12}, entt::entity{42}}; + int lhs_values[3u]{3, 6, 9}; + lhs.insert(std::begin(lhs_entities), std::end(lhs_entities), lhs_values); - ASSERT_EQ(std::as_const(lhs).get(3), 3); - ASSERT_EQ(std::as_const(lhs).get(12), 6); - ASSERT_EQ(std::as_const(lhs).get(42), 9); - ASSERT_EQ(rhs.get(12), 6); + ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end())); + ASSERT_TRUE(std::equal(std::rbegin(lhs_values), std::rend(lhs_values), lhs.begin(), lhs.end())); lhs.respect(rhs); - ASSERT_EQ(*(std::as_const(lhs).raw() + 0u), 3); - ASSERT_EQ(*(std::as_const(lhs).raw() + 1u), 9); - ASSERT_EQ(*(std::as_const(lhs).raw() + 2u), 6); + ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end())); + ASSERT_TRUE(std::equal(std::rbegin(lhs_values), std::rend(lhs_values), lhs.begin(), lhs.end())); +} + +TEST(Storage, RespectOverlap) { + entt::storage lhs; + entt::storage rhs; + + entt::entity lhs_entities[3u]{entt::entity{3}, entt::entity{12}, entt::entity{42}}; + int lhs_values[3u]{3, 6, 9}; + lhs.insert(std::begin(lhs_entities), std::end(lhs_entities), lhs_values); + + entt::entity rhs_entities[1u]{entt::entity{12}}; + int rhs_values[1u]{6}; + rhs.insert(std::begin(rhs_entities), std::end(rhs_entities), rhs_values); + + ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end())); + ASSERT_TRUE(std::equal(std::rbegin(lhs_values), std::rend(lhs_values), lhs.begin(), lhs.end())); + + ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.entt::sparse_set::begin(), rhs.entt::sparse_set::end())); + ASSERT_TRUE(std::equal(std::rbegin(rhs_values), std::rend(rhs_values), rhs.begin(), rhs.end())); + + lhs.respect(rhs); auto begin = lhs.begin(); auto end = lhs.end(); - ASSERT_EQ(*(begin++), 6); - ASSERT_EQ(*(begin++), 9); - ASSERT_EQ(*(begin++), 3); + ASSERT_EQ(*(begin++), lhs_values[1u]); + ASSERT_EQ(*(begin++), lhs_values[2u]); + ASSERT_EQ(*(begin++), lhs_values[0u]); ASSERT_EQ(begin, end); + + ASSERT_EQ(lhs.data()[0u], lhs_entities[0u]); + ASSERT_EQ(lhs.data()[1u], lhs_entities[2u]); + ASSERT_EQ(lhs.data()[2u], lhs_entities[1u]); } TEST(Storage, RespectOrdered) { - entt::storage lhs; - entt::storage rhs; - - lhs.construct(1, 0); - lhs.construct(2, 0); - lhs.construct(3, 0); - lhs.construct(4, 0); - lhs.construct(5, 0); - - ASSERT_EQ(lhs.get(1), 0); - ASSERT_EQ(lhs.get(2), 0); - ASSERT_EQ(lhs.get(3), 0); - ASSERT_EQ(lhs.get(4), 0); - ASSERT_EQ(lhs.get(5), 0); - - rhs.construct(6, 0); - rhs.construct(1, 0); - rhs.construct(2, 0); - rhs.construct(3, 0); - rhs.construct(4, 0); - rhs.construct(5, 0); - - ASSERT_EQ(rhs.get(6), 0); - ASSERT_EQ(rhs.get(1), 0); - ASSERT_EQ(rhs.get(2), 0); - ASSERT_EQ(rhs.get(3), 0); - ASSERT_EQ(rhs.get(4), 0); - ASSERT_EQ(rhs.get(5), 0); + entt::storage lhs; + entt::storage rhs; - rhs.respect(lhs); + entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}}; + int lhs_values[5u]{1, 2, 3, 4, 5}; + lhs.insert(std::begin(lhs_entities), std::end(lhs_entities), lhs_values); + + entt::entity rhs_entities[6u]{entt::entity{6}, entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}}; + int rhs_values[6u]{6, 1, 2, 3, 4, 5}; + rhs.insert(std::begin(rhs_entities), std::end(rhs_entities), rhs_values); - ASSERT_EQ(*(lhs.data() + 0u), 1u); - ASSERT_EQ(*(lhs.data() + 1u), 2u); - ASSERT_EQ(*(lhs.data() + 2u), 3u); - ASSERT_EQ(*(lhs.data() + 3u), 4u); - ASSERT_EQ(*(lhs.data() + 4u), 5u); + ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end())); + ASSERT_TRUE(std::equal(std::rbegin(lhs_values), std::rend(lhs_values), lhs.begin(), lhs.end())); - ASSERT_EQ(*(rhs.data() + 0u), 6u); - ASSERT_EQ(*(rhs.data() + 1u), 1u); - ASSERT_EQ(*(rhs.data() + 2u), 2u); - ASSERT_EQ(*(rhs.data() + 3u), 3u); - ASSERT_EQ(*(rhs.data() + 4u), 4u); - ASSERT_EQ(*(rhs.data() + 5u), 5u); + ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.entt::sparse_set::begin(), rhs.entt::sparse_set::end())); + ASSERT_TRUE(std::equal(std::rbegin(rhs_values), std::rend(rhs_values), rhs.begin(), rhs.end())); + + rhs.respect(lhs); + + ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.entt::sparse_set::begin(), rhs.entt::sparse_set::end())); + ASSERT_TRUE(std::equal(std::rbegin(rhs_values), std::rend(rhs_values), rhs.begin(), rhs.end())); } TEST(Storage, RespectReverse) { - entt::storage lhs; - entt::storage rhs; - - lhs.construct(1, 0); - lhs.construct(2, 0); - lhs.construct(3, 0); - lhs.construct(4, 0); - lhs.construct(5, 0); - - ASSERT_EQ(lhs.get(1), 0); - ASSERT_EQ(lhs.get(2), 0); - ASSERT_EQ(lhs.get(3), 0); - ASSERT_EQ(lhs.get(4), 0); - ASSERT_EQ(lhs.get(5), 0); - - rhs.construct(5, 0); - rhs.construct(4, 0); - rhs.construct(3, 0); - rhs.construct(2, 0); - rhs.construct(1, 0); - rhs.construct(6, 0); - - ASSERT_EQ(rhs.get(5), 0); - ASSERT_EQ(rhs.get(4), 0); - ASSERT_EQ(rhs.get(3), 0); - ASSERT_EQ(rhs.get(2), 0); - ASSERT_EQ(rhs.get(1), 0); - ASSERT_EQ(rhs.get(6), 0); + entt::storage lhs; + entt::storage rhs; - rhs.respect(lhs); + entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}}; + int lhs_values[5u]{1, 2, 3, 4, 5}; + lhs.insert(std::begin(lhs_entities), std::end(lhs_entities), lhs_values); - ASSERT_EQ(*(lhs.data() + 0u), 1u); - ASSERT_EQ(*(lhs.data() + 1u), 2u); - ASSERT_EQ(*(lhs.data() + 2u), 3u); - ASSERT_EQ(*(lhs.data() + 3u), 4u); - ASSERT_EQ(*(lhs.data() + 4u), 5u); + entt::entity rhs_entities[6u]{entt::entity{5}, entt::entity{4}, entt::entity{3}, entt::entity{2}, entt::entity{1}, entt::entity{6}}; + int rhs_values[6u]{5, 4, 3, 2, 1, 6}; + rhs.insert(std::begin(rhs_entities), std::end(rhs_entities), rhs_values); - ASSERT_EQ(*(rhs.data() + 0u), 6u); - ASSERT_EQ(*(rhs.data() + 1u), 1u); - ASSERT_EQ(*(rhs.data() + 2u), 2u); - ASSERT_EQ(*(rhs.data() + 3u), 3u); - ASSERT_EQ(*(rhs.data() + 4u), 4u); - ASSERT_EQ(*(rhs.data() + 5u), 5u); -} + ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end())); + ASSERT_TRUE(std::equal(std::rbegin(lhs_values), std::rend(lhs_values), lhs.begin(), lhs.end())); -TEST(Storage, RespectUnordered) { - entt::storage lhs; - entt::storage rhs; - - lhs.construct(1, 0); - lhs.construct(2, 0); - lhs.construct(3, 0); - lhs.construct(4, 0); - lhs.construct(5, 0); - - ASSERT_EQ(lhs.get(1), 0); - ASSERT_EQ(lhs.get(2), 0); - ASSERT_EQ(lhs.get(3), 0); - ASSERT_EQ(lhs.get(4), 0); - ASSERT_EQ(lhs.get(5), 0); - - rhs.construct(3, 0); - rhs.construct(2, 0); - rhs.construct(6, 0); - rhs.construct(1, 0); - rhs.construct(4, 0); - rhs.construct(5, 0); - - ASSERT_EQ(rhs.get(3), 0); - ASSERT_EQ(rhs.get(2), 0); - ASSERT_EQ(rhs.get(6), 0); - ASSERT_EQ(rhs.get(1), 0); - ASSERT_EQ(rhs.get(4), 0); - ASSERT_EQ(rhs.get(5), 0); + ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.entt::sparse_set::begin(), rhs.entt::sparse_set::end())); + ASSERT_TRUE(std::equal(std::rbegin(rhs_values), std::rend(rhs_values), rhs.begin(), rhs.end())); rhs.respect(lhs); - ASSERT_EQ(*(lhs.data() + 0u), 1u); - ASSERT_EQ(*(lhs.data() + 1u), 2u); - ASSERT_EQ(*(lhs.data() + 2u), 3u); - ASSERT_EQ(*(lhs.data() + 3u), 4u); - ASSERT_EQ(*(lhs.data() + 4u), 5u); + auto begin = rhs.begin(); + auto end = rhs.end(); - ASSERT_EQ(*(rhs.data() + 0u), 6u); - ASSERT_EQ(*(rhs.data() + 1u), 1u); - ASSERT_EQ(*(rhs.data() + 2u), 2u); - ASSERT_EQ(*(rhs.data() + 3u), 3u); - ASSERT_EQ(*(rhs.data() + 4u), 4u); - ASSERT_EQ(*(rhs.data() + 5u), 5u); + ASSERT_EQ(*(begin++), rhs_values[0u]); + ASSERT_EQ(*(begin++), rhs_values[1u]); + ASSERT_EQ(*(begin++), rhs_values[2u]); + ASSERT_EQ(*(begin++), rhs_values[3u]); + ASSERT_EQ(*(begin++), rhs_values[4u]); + ASSERT_EQ(*(begin++), rhs_values[5u]); + ASSERT_EQ(begin, end); + + ASSERT_EQ(rhs.data()[0u], rhs_entities[5u]); + ASSERT_EQ(rhs.data()[1u], rhs_entities[4u]); + ASSERT_EQ(rhs.data()[2u], rhs_entities[3u]); + ASSERT_EQ(rhs.data()[3u], rhs_entities[2u]); + ASSERT_EQ(rhs.data()[4u], rhs_entities[1u]); + ASSERT_EQ(rhs.data()[5u], rhs_entities[0u]); } -TEST(Storage, RespectOverlapEmptyType) { - entt::storage lhs; - entt::storage rhs; +TEST(Storage, RespectUnordered) { + entt::storage lhs; + entt::storage rhs; - lhs.construct(3); - lhs.construct(12); - lhs.construct(42); + entt::entity lhs_entities[5u]{entt::entity{1}, entt::entity{2}, entt::entity{3}, entt::entity{4}, entt::entity{5}}; + int lhs_values[5u]{1, 2, 3, 4, 5}; + lhs.insert(std::begin(lhs_entities), std::end(lhs_entities), lhs_values); - rhs.construct(12); + entt::entity rhs_entities[6u]{entt::entity{3}, entt::entity{2}, entt::entity{6}, entt::entity{1}, entt::entity{4}, entt::entity{5}}; + int rhs_values[6u]{3, 2, 6, 1, 4, 5}; + rhs.insert(std::begin(rhs_entities), std::end(rhs_entities), rhs_values); - ASSERT_EQ(lhs.sparse_set::get(3), 0u); - ASSERT_EQ(lhs.sparse_set::get(12), 1u); - ASSERT_EQ(lhs.sparse_set::get(42), 2u); + ASSERT_TRUE(std::equal(std::rbegin(lhs_entities), std::rend(lhs_entities), lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end())); + ASSERT_TRUE(std::equal(std::rbegin(lhs_values), std::rend(lhs_values), lhs.begin(), lhs.end())); - lhs.respect(rhs); + ASSERT_TRUE(std::equal(std::rbegin(rhs_entities), std::rend(rhs_entities), rhs.entt::sparse_set::begin(), rhs.entt::sparse_set::end())); + ASSERT_TRUE(std::equal(std::rbegin(rhs_values), std::rend(rhs_values), rhs.begin(), rhs.end())); + + rhs.respect(lhs); + + auto begin = rhs.begin(); + auto end = rhs.end(); - ASSERT_EQ(std::as_const(lhs).sparse_set::get(3), 0u); - ASSERT_EQ(std::as_const(lhs).sparse_set::get(12), 2u); - ASSERT_EQ(std::as_const(lhs).sparse_set::get(42), 1u); + ASSERT_EQ(*(begin++), rhs_values[5u]); + ASSERT_EQ(*(begin++), rhs_values[4u]); + ASSERT_EQ(*(begin++), rhs_values[0u]); + ASSERT_EQ(*(begin++), rhs_values[1u]); + ASSERT_EQ(*(begin++), rhs_values[3u]); + ASSERT_EQ(*(begin++), rhs_values[2u]); + ASSERT_EQ(begin, end); + + ASSERT_EQ(rhs.data()[0u], rhs_entities[2u]); + ASSERT_EQ(rhs.data()[1u], rhs_entities[3u]); + ASSERT_EQ(rhs.data()[2u], rhs_entities[1u]); + ASSERT_EQ(rhs.data()[3u], rhs_entities[0u]); + ASSERT_EQ(rhs.data()[4u], rhs_entities[4u]); + ASSERT_EQ(rhs.data()[5u], rhs_entities[5u]); } TEST(Storage, CanModifyDuringIteration) { - entt::storage set; - set.construct(0, 42); + entt::storage pool; + auto *ptr = &pool.emplace(entt::entity{0}, 42); - ASSERT_EQ(set.capacity(), (entt::storage::size_type{1})); + ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE); - const auto it = set.cbegin(); - set.reserve(entt::storage::size_type{2}); + const auto it = pool.cbegin(); + pool.reserve(ENTT_PACKED_PAGE + 1u); - ASSERT_EQ(set.capacity(), (entt::storage::size_type{2})); + ASSERT_EQ(pool.capacity(), 2 * ENTT_PACKED_PAGE); + ASSERT_EQ(&pool.get(entt::entity{0}), ptr); // this should crash with asan enabled if we break the constraint - const auto entity = *it; - (void)entity; + [[maybe_unused]] const int &value = *it; } TEST(Storage, ReferencesGuaranteed) { - entt::storage set; + entt::storage pool; - set.construct(0, 0); - set.construct(1, 1); + pool.emplace(entt::entity{0}, 0); + pool.emplace(entt::entity{1}, 1); - ASSERT_EQ(set.get(0).value, 0); - ASSERT_EQ(set.get(1).value, 1); + ASSERT_EQ(pool.get(entt::entity{0}).value, 0); + ASSERT_EQ(pool.get(entt::entity{1}).value, 1); - for(auto &&type: set) { + for(auto &&type: pool) { if(type.value) { type.value = 42; } } - ASSERT_EQ(set.get(0).value, 0); - ASSERT_EQ(set.get(1).value, 42); + ASSERT_EQ(pool.get(entt::entity{0}).value, 0); + ASSERT_EQ(pool.get(entt::entity{1}).value, 42); - auto begin = set.begin(); + auto begin = pool.begin(); - while(begin != set.end()) { + while(begin != pool.end()) { (begin++)->value = 3; } - ASSERT_EQ(set.get(0).value, 3); - ASSERT_EQ(set.get(1).value, 3); + ASSERT_EQ(pool.get(entt::entity{0}).value, 3); + ASSERT_EQ(pool.get(entt::entity{1}).value, 3); } TEST(Storage, MoveOnlyComponent) { // the purpose is to ensure that move only components are always accepted - entt::storage> set; - (void)set; + [[maybe_unused]] entt::storage> pool; } -TEST(Storage, ConstructorExceptionDoesNotAddToSet) { - entt::storage set; +TEST(Storage, UpdateFromDestructor) { + static constexpr auto size = 10u; - try { - set.construct(0); - FAIL() << "Expected constructor_exception to be thrown"; - } catch (const throwing_component::constructor_exception &) { - ASSERT_TRUE(set.empty()); - } + auto test = [](const auto target) { + entt::storage pool; + + for(std::size_t next{}; next < size; ++next) { + const auto entity = entt::entity(next); + pool.emplace(entity, pool, entity == entt::entity(size / 2) ? target : entity); + } + + pool.erase(entt::entity(size / 2)); + + ASSERT_EQ(pool.size(), size - 1u - (target != entt::null)); + ASSERT_FALSE(pool.contains(entt::entity(size / 2))); + ASSERT_FALSE(pool.contains(target)); + + pool.clear(); + + ASSERT_TRUE(pool.empty()); + + for(std::size_t next{}; next < size; ++next) { + ASSERT_FALSE(pool.contains(entt::entity(next))); + } + }; + + test(entt::entity(size - 1u)); + test(entt::entity(size - 2u)); + test(entt::entity{0u}); } + +TEST(Storage, CreateFromConstructor) { + entt::storage pool; + const entt::entity entity{0u}; + const entt::entity other{1u}; + + pool.emplace(entity, pool, other); + + ASSERT_EQ(pool.get(entity).child, other); + ASSERT_EQ(pool.get(other).child, static_cast(entt::null)); +} + +TEST(Storage, CustomAllocator) { + auto test = [](auto pool, auto alloc) { + pool.reserve(1u); + + ASSERT_NE(pool.capacity(), 0u); + + pool.emplace(entt::entity{0}); + pool.emplace(entt::entity{1}); + + decltype(pool) other{std::move(pool), alloc}; + + ASSERT_TRUE(pool.empty()); + ASSERT_FALSE(other.empty()); + ASSERT_EQ(pool.capacity(), 0u); + ASSERT_NE(other.capacity(), 0u); + ASSERT_EQ(other.size(), 2u); + + pool = std::move(other); + + ASSERT_FALSE(pool.empty()); + ASSERT_TRUE(other.empty()); + ASSERT_EQ(other.capacity(), 0u); + ASSERT_NE(pool.capacity(), 0u); + ASSERT_EQ(pool.size(), 2u); + + pool.swap(other); + pool = std::move(other); + + ASSERT_FALSE(pool.empty()); + ASSERT_TRUE(other.empty()); + ASSERT_EQ(other.capacity(), 0u); + ASSERT_NE(pool.capacity(), 0u); + ASSERT_EQ(pool.size(), 2u); + + pool.clear(); + + ASSERT_NE(pool.capacity(), 0u); + ASSERT_EQ(pool.size(), 0u); + }; + + test::throwing_allocator allocator{}; + + test(entt::basic_storage>{allocator}, allocator); + test(entt::basic_storage>{allocator}, allocator); + test(entt::basic_storage>{allocator}, allocator); +} + +TEST(Storage, ThrowingAllocator) { + auto test = [](auto pool) { + using pool_allocator_type = typename decltype(pool)::allocator_type; + using value_type = typename decltype(pool)::value_type; + + typename std::decay_t::base_type &base = pool; + + pool_allocator_type::trigger_on_allocate = true; + + ASSERT_THROW(pool.reserve(1u), typename pool_allocator_type::exception_type); + ASSERT_EQ(pool.capacity(), 0u); + + pool_allocator_type::trigger_after_allocate = true; + + ASSERT_THROW(pool.reserve(2 * ENTT_PACKED_PAGE), typename pool_allocator_type::exception_type); + ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE); + + pool.shrink_to_fit(); + + ASSERT_EQ(pool.capacity(), 0u); + + test::throwing_allocator::trigger_on_allocate = true; + + ASSERT_THROW(pool.emplace(entt::entity{0}, 0), test::throwing_allocator::exception_type); + ASSERT_FALSE(pool.contains(entt::entity{0})); + ASSERT_TRUE(pool.empty()); + + test::throwing_allocator::trigger_on_allocate = true; + + ASSERT_THROW(base.emplace(entt::entity{0}), test::throwing_allocator::exception_type); + ASSERT_FALSE(base.contains(entt::entity{0})); + ASSERT_TRUE(base.empty()); + + pool_allocator_type::trigger_on_allocate = true; + + ASSERT_THROW(pool.emplace(entt::entity{0}, 0), typename pool_allocator_type::exception_type); + ASSERT_FALSE(pool.contains(entt::entity{0})); + ASSERT_NO_THROW(pool.compact()); + ASSERT_TRUE(pool.empty()); + + pool.emplace(entt::entity{0}, 0); + const entt::entity entities[2u]{entt::entity{1}, entt::entity{ENTT_SPARSE_PAGE}}; + test::throwing_allocator::trigger_after_allocate = true; + + ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), value_type{0}), test::throwing_allocator::exception_type); + ASSERT_TRUE(pool.contains(entt::entity{1})); + ASSERT_FALSE(pool.contains(entt::entity{ENTT_SPARSE_PAGE})); + + pool.erase(entt::entity{1}); + const value_type components[2u]{value_type{1}, value_type{ENTT_SPARSE_PAGE}}; + test::throwing_allocator::trigger_on_allocate = true; + pool.compact(); + + ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), std::begin(components)), test::throwing_allocator::exception_type); + ASSERT_TRUE(pool.contains(entt::entity{1})); + ASSERT_FALSE(pool.contains(entt::entity{ENTT_SPARSE_PAGE})); + }; + + test(entt::basic_storage>{}); + test(entt::basic_storage>{}); +} + +TEST(Storage, ThrowingComponent) { + entt::storage pool; + test::throwing_type::trigger_on_value = 42; + + // strong exception safety + ASSERT_THROW(pool.emplace(entt::entity{0}, test::throwing_type{42}), typename test::throwing_type::exception_type); + ASSERT_TRUE(pool.empty()); + + const entt::entity entities[2u]{entt::entity{42}, entt::entity{1}}; + const test::throwing_type components[2u]{42, 1}; + + // basic exception safety + ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), test::throwing_type{42}), typename test::throwing_type::exception_type); + ASSERT_EQ(pool.size(), 0u); + ASSERT_FALSE(pool.contains(entt::entity{1})); + + // basic exception safety + ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), std::begin(components)), typename test::throwing_type::exception_type); + ASSERT_EQ(pool.size(), 0u); + ASSERT_FALSE(pool.contains(entt::entity{1})); + + // basic exception safety + ASSERT_THROW(pool.insert(std::rbegin(entities), std::rend(entities), std::rbegin(components)), typename test::throwing_type::exception_type); + ASSERT_EQ(pool.size(), 1u); + ASSERT_TRUE(pool.contains(entt::entity{1})); + ASSERT_EQ(pool.get(entt::entity{1}), 1); + + pool.clear(); + pool.emplace(entt::entity{1}, 1); + pool.emplace(entt::entity{42}, 42); + + // basic exception safety + ASSERT_THROW(pool.erase(entt::entity{1}), typename test::throwing_type::exception_type); + ASSERT_EQ(pool.size(), 2u); + ASSERT_TRUE(pool.contains(entt::entity{42})); + ASSERT_TRUE(pool.contains(entt::entity{1})); + ASSERT_EQ(pool.at(0u), entt::entity{1}); + ASSERT_EQ(pool.at(1u), entt::entity{42}); + ASSERT_EQ(pool.get(entt::entity{42}), 42); + // the element may have been moved but it's still there + ASSERT_EQ(pool.get(entt::entity{1}), test::throwing_type::moved_from_value); + + test::throwing_type::trigger_on_value = 99; + pool.erase(entt::entity{1}); + + ASSERT_EQ(pool.size(), 1u); + ASSERT_TRUE(pool.contains(entt::entity{42})); + ASSERT_FALSE(pool.contains(entt::entity{1})); + ASSERT_EQ(pool.at(0u), entt::entity{42}); + ASSERT_EQ(pool.get(entt::entity{42}), 42); +} + +#if defined(ENTT_HAS_TRACKED_MEMORY_RESOURCE) + +TEST(Storage, NoUsesAllocatorConstruction) { + test::tracked_memory_resource memory_resource{}; + entt::basic_storage> pool{&memory_resource}; + const entt::entity entity{}; + + pool.emplace(entity); + pool.erase(entity); + memory_resource.reset(); + pool.emplace(entity, 0); + + ASSERT_TRUE(pool.get_allocator().resource()->is_equal(memory_resource)); + ASSERT_EQ(memory_resource.do_allocate_counter(), 0u); + ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u); +} + +TEST(Storage, UsesAllocatorConstruction) { + using string_type = typename test::tracked_memory_resource::string_type; + + test::tracked_memory_resource memory_resource{}; + entt::basic_storage> pool{&memory_resource}; + const entt::entity entity{}; + + pool.emplace(entity); + pool.erase(entity); + memory_resource.reset(); + pool.emplace(entity, test::tracked_memory_resource::default_value); + + ASSERT_TRUE(pool.get_allocator().resource()->is_equal(memory_resource)); + ASSERT_GT(memory_resource.do_allocate_counter(), 0u); + ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u); +} + +#endif diff --git a/modules/entt/test/entt/entity/view.cpp b/modules/entt/test/entt/entity/view.cpp index 91ed272..92caa0b 100644 --- a/modules/entt/test/entt/entity/view.cpp +++ b/modules/entt/test/entt/entity/view.cpp @@ -1,10 +1,19 @@ -#include +#include +#include +#include #include +#include #include -#include #include #include +struct empty_type {}; + +struct stable_type { + static constexpr auto in_place_delete = true; + int value; +}; + TEST(SingleComponentView, Functionalities) { entt::registry registry; auto view = registry.view(); @@ -15,39 +24,91 @@ TEST(SingleComponentView, Functionalities) { ASSERT_TRUE(view.empty()); - registry.assign(e1); - registry.assign(e1); + registry.emplace(e1); + registry.emplace(e1); - ASSERT_NO_THROW(registry.view().begin()++); - ASSERT_NO_THROW(++registry.view().begin()); + ASSERT_NO_FATAL_FAILURE(view.begin()++); + ASSERT_NO_FATAL_FAILURE(++cview.begin()); + ASSERT_NO_FATAL_FAILURE([](auto it) { return it++; }(view.rbegin())); + ASSERT_NO_FATAL_FAILURE([](auto it) { return ++it; }(cview.rbegin())); ASSERT_NE(view.begin(), view.end()); ASSERT_NE(cview.begin(), cview.end()); - ASSERT_EQ(view.size(), typename decltype(view)::size_type{1}); + ASSERT_NE(view.rbegin(), view.rend()); + ASSERT_NE(cview.rbegin(), cview.rend()); + ASSERT_EQ(view.size(), 1u); ASSERT_FALSE(view.empty()); - registry.assign(e0); + registry.emplace(e0); + + ASSERT_EQ(view.size(), 2u); - ASSERT_EQ(view.size(), typename decltype(view)::size_type{2}); + view.get(e0) = '1'; + std::get<0>(view.get(e1)) = '2'; - view.get(e0) = '1'; - view.get(e1) = '2'; + ASSERT_EQ(view.get<0u>(e0), '1'); + ASSERT_EQ(cview.get<0u>(e0), view.get(e0)); + ASSERT_EQ(view.get(e1), '2'); for(auto entity: view) { - ASSERT_TRUE(cview.get(entity) == '1' || cview.get(entity) == '2'); + ASSERT_TRUE(entity == e0 || entity == e1); + ASSERT_TRUE(entity != e0 || cview.get(entity) == '1'); + ASSERT_TRUE(entity != e1 || std::get(cview.get(entity)) == '2'); } - ASSERT_EQ(*(view.data() + 0), e1); - ASSERT_EQ(*(view.data() + 1), e0); - - ASSERT_EQ(*(view.raw() + 0), '2'); - ASSERT_EQ(*(cview.raw() + 1), '1'); - - registry.remove(e0); - registry.remove(e1); + registry.erase(e0); + registry.erase(e1); ASSERT_EQ(view.begin(), view.end()); + ASSERT_EQ(view.rbegin(), view.rend()); ASSERT_TRUE(view.empty()); + + decltype(view) invalid{}; + + ASSERT_TRUE(view); + ASSERT_TRUE(cview); + ASSERT_FALSE(invalid); +} + +TEST(SingleComponentView, Handle) { + entt::registry registry; + const auto entity = registry.create(); + + auto view = registry.view(); + auto &&handle = view.handle(); + + ASSERT_TRUE(handle.empty()); + ASSERT_FALSE(handle.contains(entity)); + ASSERT_EQ(&handle, &view.handle()); + + registry.emplace(entity); + + ASSERT_FALSE(handle.empty()); + ASSERT_TRUE(handle.contains(entity)); + ASSERT_EQ(&handle, &view.handle()); +} + +TEST(SingleComponentView, LazyTypeFromConstRegistry) { + entt::registry registry{}; + auto eview = std::as_const(registry).view(); + auto cview = std::as_const(registry).view(); + + const auto entity = registry.create(); + registry.emplace(entity); + registry.emplace(entity); + + ASSERT_TRUE(cview); + ASSERT_TRUE(eview); + + ASSERT_TRUE(cview.empty()); + ASSERT_EQ(eview.size(), 0u); + ASSERT_FALSE(cview.contains(entity)); + + ASSERT_EQ(cview.begin(), cview.end()); + ASSERT_EQ(eview.rbegin(), eview.rend()); + ASSERT_EQ(eview.find(entity), eview.end()); + ASSERT_NE(cview.front(), entity); + ASSERT_NE(eview.back(), entity); } TEST(SingleComponentView, ElementAccess) { @@ -56,25 +117,28 @@ TEST(SingleComponentView, ElementAccess) { auto cview = std::as_const(registry).view(); const auto e0 = registry.create(); - registry.assign(e0); + registry.emplace(e0, 42); const auto e1 = registry.create(); - registry.assign(e1); + registry.emplace(e1, 3); - for(typename decltype(view)::size_type i{}; i < view.size(); ++i) { + for(auto i = 0u; i < view.size(); ++i) { ASSERT_EQ(view[i], i ? e0 : e1); ASSERT_EQ(cview[i], i ? e0 : e1); } + + ASSERT_EQ(view[e0], 42); + ASSERT_EQ(cview[e1], 3); } TEST(SingleComponentView, Contains) { entt::registry registry; const auto e0 = registry.create(); - registry.assign(e0); + registry.emplace(e0); const auto e1 = registry.create(); - registry.assign(e1); + registry.emplace(e1); registry.destroy(e0); @@ -88,36 +152,61 @@ TEST(SingleComponentView, Empty) { entt::registry registry; const auto e0 = registry.create(); - registry.assign(e0); - registry.assign(e0); + registry.emplace(e0); + registry.emplace(e0); const auto e1 = registry.create(); - registry.assign(e1); + registry.emplace(e1); auto view = registry.view(); - ASSERT_EQ(view.size(), entt::registry::size_type{0}); + ASSERT_EQ(view.size(), 0u); ASSERT_EQ(view.begin(), view.end()); + ASSERT_EQ(view.rbegin(), view.rend()); } TEST(SingleComponentView, Each) { entt::registry registry; - - registry.assign(registry.create()); - registry.assign(registry.create()); + entt::entity entity[2]{registry.create(), registry.create()}; auto view = registry.view(); - std::size_t cnt = 0; + auto cview = std::as_const(registry).view(); + + registry.emplace(entity[0u], 0); + registry.emplace(entity[1u], 1); + + auto iterable = view.each(); + auto citerable = cview.each(); - view.each([&cnt](auto, int &) { ++cnt; }); - view.each([&cnt](int &) { ++cnt; }); + ASSERT_NE(citerable.begin(), citerable.end()); + ASSERT_NO_THROW(iterable.begin()->operator=(*iterable.begin())); + ASSERT_EQ(decltype(iterable.end()){}, iterable.end()); - ASSERT_EQ(cnt, std::size_t{4}); + auto it = iterable.begin(); - std::as_const(view).each([&cnt](auto, const int &) { --cnt; }); - std::as_const(view).each([&cnt](const int &) { --cnt; }); + ASSERT_EQ((it++, ++it), iterable.end()); - ASSERT_EQ(cnt, std::size_t{0}); + view.each([expected = 1u](auto entt, int &value) mutable { + ASSERT_EQ(entt::to_integral(entt), expected); + ASSERT_EQ(value, expected); + --expected; + }); + + cview.each([expected = 1u](const int &value) mutable { + ASSERT_EQ(value, expected); + --expected; + }); + + ASSERT_EQ(std::get<0>(*iterable.begin()), entity[1u]); + ASSERT_EQ(std::get<0>(*++citerable.begin()), entity[0u]); + + static_assert(std::is_same_v(*iterable.begin())), int &>); + static_assert(std::is_same_v(*citerable.begin())), const int &>); + + // do not use iterable, make sure an iterable view works when created from a temporary + for(auto [entt, value]: view.each()) { + ASSERT_EQ(entt::to_integral(entt), value); + } } TEST(SingleComponentView, ConstNonConstAndAllInBetween) { @@ -125,29 +214,68 @@ TEST(SingleComponentView, ConstNonConstAndAllInBetween) { auto view = registry.view(); auto cview = std::as_const(registry).view(); - ASSERT_EQ(view.size(), decltype(view.size()){0}); - ASSERT_EQ(cview.size(), decltype(cview.size()){0}); + ASSERT_EQ(view.size(), 0u); + ASSERT_EQ(cview.size(), 0u); + + registry.emplace(registry.create(), 0); - registry.assign(registry.create(), 0); + ASSERT_EQ(view.size(), 1u); + ASSERT_EQ(cview.size(), 1u); - ASSERT_EQ(view.size(), decltype(view.size()){1}); - ASSERT_EQ(cview.size(), decltype(cview.size()){1}); + static_assert(std::is_same_v({})), int &>); + static_assert(std::is_same_v({})), int &>); + static_assert(std::is_same_v>); - ASSERT_TRUE((std::is_same_v)); - ASSERT_TRUE((std::is_same_v)); + static_assert(std::is_same_v({})), const int &>); + static_assert(std::is_same_v({})), const int &>); + static_assert(std::is_same_v>); - ASSERT_TRUE((std::is_same_v)); - ASSERT_TRUE((std::is_same_v)); - ASSERT_TRUE((std::is_same_v)); - ASSERT_TRUE((std::is_same_v)); + static_assert(std::is_same_v()), decltype(cview)>); - view.each([](auto, auto &&i) { - ASSERT_TRUE((std::is_same_v)); + view.each([](auto &&i) { + static_assert(std::is_same_v); }); - cview.each([](auto, auto &&i) { - ASSERT_TRUE((std::is_same_v)); + cview.each([](auto &&i) { + static_assert(std::is_same_v); }); + + for(auto [entt, iv]: view.each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } + + for(auto [entt, iv]: cview.each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } +} + +TEST(SingleComponentView, ConstNonConstAndAllInBetweenWithEmptyType) { + entt::registry registry; + auto view = registry.view(); + auto cview = std::as_const(registry).view(); + + ASSERT_EQ(view.size(), 0u); + ASSERT_EQ(cview.size(), 0u); + + registry.emplace(registry.create()); + + ASSERT_EQ(view.size(), 1u); + ASSERT_EQ(cview.size(), 1u); + + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + + static_assert(std::is_same_v()), decltype(cview)>); + + for(auto [entt]: view.each()) { + static_assert(std::is_same_v); + } + + for(auto [entt]: cview.each()) { + static_assert(std::is_same_v); + } } TEST(SingleComponentView, Find) { @@ -155,18 +283,18 @@ TEST(SingleComponentView, Find) { auto view = registry.view(); const auto e0 = registry.create(); - registry.assign(e0); + registry.emplace(e0); const auto e1 = registry.create(); - registry.assign(e1); + registry.emplace(e1); const auto e2 = registry.create(); - registry.assign(e2); + registry.emplace(e2); const auto e3 = registry.create(); - registry.assign(e3); + registry.emplace(e3); - registry.remove(e1); + registry.erase(e1); ASSERT_NE(view.find(e0), view.end()); ASSERT_EQ(view.find(e1), view.end()); @@ -184,101 +312,289 @@ TEST(SingleComponentView, Find) { const auto e4 = registry.create(); registry.destroy(e4); const auto e5 = registry.create(); - registry.assign(e5); + registry.emplace(e5); ASSERT_NE(view.find(e5), view.end()); ASSERT_EQ(view.find(e4), view.end()); } -TEST(SingleComponentView, Less) { +TEST(SingleComponentView, EmptyTypes) { entt::registry registry; - const auto entity = std::get<0>(registry.create>()); - registry.create(); + entt::entity entities[2u]; - registry.view>().less([entity](const auto entt) { - ASSERT_EQ(entity, entt); + registry.create(std::begin(entities), std::end(entities)); + registry.emplace(entities[0u], 0); + registry.emplace(entities[0u]); + registry.emplace(entities[1u], 'c'); + + registry.view().each([&](const auto entt) { + ASSERT_EQ(entities[0u], entt); }); - registry.view>().less([check = true]() mutable { + registry.view().each([check = true]() mutable { ASSERT_TRUE(check); check = false; }); - registry.view().less([entity](const auto entt, int) { - ASSERT_EQ(entity, entt); + for(auto [entt]: registry.view().each()) { + static_assert(std::is_same_v); + ASSERT_EQ(entities[0u], entt); + } + + registry.view().each([&](const auto entt, int) { + ASSERT_EQ(entities[0u], entt); }); - registry.view().less([check = true](int) mutable { + registry.view().each([check = true](int) mutable { ASSERT_TRUE(check); check = false; }); + + for(auto [entt, iv]: registry.view().each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_EQ(entities[0u], entt); + } } -TEST(MultipleComponentView, Functionalities) { +TEST(SingleComponentView, FrontBack) { entt::registry registry; - auto view = registry.view(); - auto cview = std::as_const(registry).view(); + auto view = registry.view(); - ASSERT_TRUE(view.empty()); - ASSERT_TRUE(view.empty()); - ASSERT_TRUE(cview.empty()); + ASSERT_EQ(view.front(), static_cast(entt::null)); + ASSERT_EQ(view.back(), static_cast(entt::null)); const auto e0 = registry.create(); - registry.assign(e0); + registry.emplace(e0); const auto e1 = registry.create(); - registry.assign(e1); + registry.emplace(e1); - ASSERT_FALSE(view.empty()); - ASSERT_FALSE(view.empty()); - ASSERT_FALSE(cview.empty()); + ASSERT_EQ(view.front(), e1); + ASSERT_EQ(view.back(), e0); +} + +TEST(SingleComponentView, DeductionGuide) { + entt::registry registry; + typename entt::storage_traits::storage_type istorage; + typename entt::storage_traits::storage_type sstorage; + + static_assert(std::is_same_v, entt::exclude_t<>>, decltype(entt::basic_view{istorage})>); + static_assert(std::is_same_v, entt::exclude_t<>>, decltype(entt::basic_view{std::as_const(istorage)})>); + static_assert(std::is_same_v, entt::exclude_t<>>, decltype(entt::basic_view{sstorage})>); +} + +TEST(SingleComponentView, IterableViewAlgorithmCompatibility) { + entt::registry registry; + const auto entity = registry.create(); + + registry.emplace(entity); + + const auto view = registry.view(); + const auto iterable = view.each(); + const auto it = std::find_if(iterable.begin(), iterable.end(), [entity](auto args) { return std::get<0>(args) == entity; }); + + ASSERT_EQ(std::get<0>(*it), entity); +} + +TEST(SingleComponentView, StableType) { + entt::registry registry; + auto view = registry.view(); + + const auto entity = registry.create(); + const auto other = registry.create(); + + registry.emplace(entity); + registry.emplace(other); + registry.destroy(entity); + + ASSERT_EQ(view.size_hint(), 2u); + ASSERT_FALSE(view.contains(entity)); + ASSERT_TRUE(view.contains(other)); - registry.assign(e1); + ASSERT_EQ(view.front(), other); + ASSERT_EQ(view.back(), other); - auto it = registry.view().begin(); + ASSERT_EQ(*view.begin(), other); + ASSERT_EQ(++view.begin(), view.end()); + + view.each([other](const auto entt, stable_type) { + ASSERT_EQ(other, entt); + }); - ASSERT_EQ(*it, e1); - ASSERT_EQ(++it, (registry.view().end())); + view.each([check = true](stable_type) mutable { + ASSERT_TRUE(check); + check = false; + }); + + for(auto [entt, st]: view.each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_EQ(other, entt); + } - ASSERT_NO_THROW((registry.view().begin()++)); - ASSERT_NO_THROW((++registry.view().begin())); + registry.compact(); + + ASSERT_EQ(view.size_hint(), 1u); +} + +TEST(SingleComponentView, Storage) { + entt::registry registry; + const auto entity = registry.create(); + const auto view = registry.view(); + const auto cview = registry.view(); + + static_assert(std::is_same_v::storage_type &>); + static_assert(std::is_same_v()), typename entt::storage_traits::storage_type &>); + static_assert(std::is_same_v()), typename entt::storage_traits::storage_type &>); + static_assert(std::is_same_v::storage_type &>); + static_assert(std::is_same_v()), const typename entt::storage_traits::storage_type &>); + static_assert(std::is_same_v()), const typename entt::storage_traits::storage_type &>); + + ASSERT_EQ(view.size(), 0u); + ASSERT_EQ(cview.size(), 0u); + + view.storage().emplace(entity); + registry.emplace(entity); + + ASSERT_EQ(view.size(), 1u); + ASSERT_EQ(cview.size(), 1u); + ASSERT_TRUE(view.storage().contains(entity)); + ASSERT_TRUE(cview.storage<0u>().contains(entity)); + ASSERT_TRUE((registry.all_of(entity))); + + view.storage().erase(entity); + + ASSERT_EQ(view.size(), 0u); + ASSERT_EQ(cview.size(), 1u); + ASSERT_FALSE(view.storage<0u>().contains(entity)); + ASSERT_TRUE(cview.storage().contains(entity)); + ASSERT_FALSE((registry.all_of(entity))); +} + +TEST(MultiComponentView, Functionalities) { + entt::registry registry; + auto view = registry.view(); + auto cview = std::as_const(registry).view(); + + const auto e0 = registry.create(); + registry.emplace(e0, '1'); + + const auto e1 = registry.create(); + registry.emplace(e1, 42); + registry.emplace(e1, '2'); + + ASSERT_EQ(*view.begin(), e1); + ASSERT_EQ(*cview.begin(), e1); + ASSERT_EQ(++view.begin(), (view.end())); + ASSERT_EQ(++cview.begin(), (cview.end())); + + ASSERT_NO_FATAL_FAILURE((view.begin()++)); + ASSERT_NO_FATAL_FAILURE((++cview.begin())); ASSERT_NE(view.begin(), view.end()); ASSERT_NE(cview.begin(), cview.end()); - ASSERT_EQ(view.size(), decltype(view.size()){1}); - ASSERT_EQ(view.size(), decltype(view.size()){1}); - ASSERT_EQ(cview.size(), decltype(view.size()){2}); - - registry.get(e0) = '1'; - registry.get(e1) = '2'; - registry.get(e1) = 42; + ASSERT_EQ(view.size_hint(), 1u); for(auto entity: view) { ASSERT_EQ(std::get<0>(cview.get(entity)), 42); + ASSERT_EQ(std::get<0>(cview.get<0u, 1u>(entity)), 42); + ASSERT_EQ(std::get<1>(view.get(entity)), '2'); + ASSERT_EQ(std::get<1>(view.get<0u, 1u>(entity)), '2'); + ASSERT_EQ(cview.get(entity), '2'); + ASSERT_EQ(cview.get<1u>(entity), '2'); } - ASSERT_EQ(*(view.data() + 0), e1); - ASSERT_EQ(*(view.data() + 0), e0); - ASSERT_EQ(*(cview.data() + 1), e1); + decltype(view) invalid{}; - ASSERT_EQ(*(view.raw() + 0), 42); - ASSERT_EQ(*(view.raw() + 0), '1'); - ASSERT_EQ(*(cview.raw() + 1), '2'); + ASSERT_TRUE(view); + ASSERT_TRUE(cview); + ASSERT_FALSE(invalid); } -TEST(MultipleComponentView, Iterator) { +TEST(MultiComponentView, Handle) { entt::registry registry; const auto entity = registry.create(); - registry.assign(entity); - registry.assign(entity); + + auto view = registry.view(); + auto &&handle = view.handle(); + + ASSERT_TRUE(handle.empty()); + ASSERT_FALSE(handle.contains(entity)); + ASSERT_EQ(&handle, &view.handle()); + + registry.emplace(entity); + + ASSERT_FALSE(handle.empty()); + ASSERT_TRUE(handle.contains(entity)); + ASSERT_EQ(&handle, &view.handle()); + + view = registry.view(); + auto &&other = view.handle(); + + ASSERT_TRUE(other.empty()); + ASSERT_FALSE(other.contains(entity)); + ASSERT_EQ(&other, &view.handle()); + ASSERT_NE(&handle, &other); + + view = view.use(); + + ASSERT_NE(&other, &view.handle()); + ASSERT_EQ(&handle, &view.handle()); +} + +TEST(MultiComponentView, LazyTypesFromConstRegistry) { + entt::registry registry{}; + auto view = std::as_const(registry).view(); + + const auto entity = registry.create(); + registry.emplace(entity); + registry.emplace(entity); + + ASSERT_TRUE(view); + + ASSERT_EQ(view.size_hint(), 0u); + ASSERT_FALSE(view.contains(entity)); + + ASSERT_EQ(view.begin(), view.end()); + ASSERT_EQ(view.find(entity), view.end()); + ASSERT_NE(view.front(), entity); + ASSERT_NE(view.back(), entity); +} + +TEST(MultiComponentView, LazyExcludedTypeFromConstRegistry) { + entt::registry registry; + + auto entity = registry.create(); + registry.emplace(entity); + + auto view = std::as_const(registry).view(entt::exclude); + + ASSERT_TRUE(view); + + ASSERT_EQ(view.size_hint(), 1u); + ASSERT_TRUE(view.contains(entity)); + + ASSERT_NE(view.begin(), view.end()); + ASSERT_NE(view.find(entity), view.end()); + ASSERT_EQ(view.front(), entity); + ASSERT_EQ(view.back(), entity); +} + +TEST(MultiComponentView, Iterator) { + entt::registry registry; + const entt::entity entity[2]{registry.create(), registry.create()}; + + registry.insert(std::begin(entity), std::end(entity)); + registry.insert(std::begin(entity), std::end(entity)); const auto view = registry.view(); - using iterator_type = typename decltype(view)::iterator_type; + using iterator = typename decltype(view)::iterator; - iterator_type end{view.begin()}; - iterator_type begin{}; + iterator end{view.begin()}; + iterator begin{}; begin = view.end(); std::swap(begin, end); @@ -286,20 +602,42 @@ TEST(MultipleComponentView, Iterator) { ASSERT_EQ(end, view.end()); ASSERT_NE(begin, end); - ASSERT_EQ(view.begin()++, view.begin()); - ASSERT_EQ(++view.begin(), view.end()); + ASSERT_EQ(*begin, entity[1u]); + ASSERT_EQ(*begin.operator->(), entity[1u]); + ASSERT_EQ(begin++, view.begin()); + + ASSERT_EQ(*begin, entity[0u]); + ASSERT_EQ(*begin.operator->(), entity[0u]); + ASSERT_EQ(++begin, view.end()); +} + +TEST(MultiComponentView, ElementAccess) { + entt::registry registry; + auto view = registry.view(); + auto cview = std::as_const(registry).view(); + + const auto e0 = registry.create(); + registry.emplace(e0, 42); + registry.emplace(e0, '0'); + + const auto e1 = registry.create(); + registry.emplace(e1, 3); + registry.emplace(e1, '1'); + + ASSERT_EQ(view[e0], std::make_tuple(42, '0')); + ASSERT_EQ(cview[e1], std::make_tuple(3, '1')); } -TEST(MultipleComponentView, Contains) { +TEST(MultiComponentView, Contains) { entt::registry registry; const auto e0 = registry.create(); - registry.assign(e0); - registry.assign(e0); + registry.emplace(e0); + registry.emplace(e0); const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(e1); + registry.emplace(e1); registry.destroy(e0); @@ -309,64 +647,88 @@ TEST(MultipleComponentView, Contains) { ASSERT_TRUE(view.contains(e1)); } -TEST(MultipleComponentView, Empty) { +TEST(MultiComponentView, SizeHint) { entt::registry registry; const auto e0 = registry.create(); - registry.assign(e0); - registry.assign(e0); - registry.assign(e0); + registry.emplace(e0); + registry.emplace(e0); + registry.emplace(e0); const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(e1); + registry.emplace(e1); auto view = registry.view(); - ASSERT_EQ(view.size(), entt::registry::size_type{1}); + ASSERT_EQ(view.size_hint(), 1u); ASSERT_EQ(view.begin(), view.end()); } -TEST(MultipleComponentView, Each) { +TEST(MultiComponentView, Each) { entt::registry registry; - - const auto e0 = registry.create(); - registry.assign(e0); - registry.assign(e0); - - const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + entt::entity entity[2]{registry.create(), registry.create()}; auto view = registry.view(); auto cview = std::as_const(registry).view(); - std::size_t cnt = 0; - view.each([&cnt](auto, int &, char &) { ++cnt; }); - view.each([&cnt](int &, char &) { ++cnt; }); + registry.emplace(entity[0u], 0); + registry.emplace(entity[0u], 0); + + registry.emplace(entity[1u], 1); + registry.emplace(entity[1u], 1); + + auto iterable = view.each(); + auto citerable = cview.each(); + + ASSERT_NE(citerable.begin(), citerable.end()); + ASSERT_NO_THROW(iterable.begin()->operator=(*iterable.begin())); + ASSERT_EQ(decltype(iterable.end()){}, iterable.end()); + + auto it = iterable.begin(); + + ASSERT_EQ((it++, ++it), iterable.end()); - ASSERT_EQ(cnt, std::size_t{4}); + view.each([expected = 1u](auto entt, int &ivalue, char &cvalue) mutable { + ASSERT_EQ(entt::to_integral(entt), expected); + ASSERT_EQ(ivalue, expected); + ASSERT_EQ(cvalue, expected); + --expected; + }); + + cview.each([expected = 1u](const int &ivalue, const char &cvalue) mutable { + ASSERT_EQ(ivalue, expected); + ASSERT_EQ(cvalue, expected); + --expected; + }); - cview.each([&cnt](auto, const int &, const char &) { --cnt; }); - cview.each([&cnt](const int &, const char &) { --cnt; }); + ASSERT_EQ(std::get<0>(*iterable.begin()), entity[1u]); + ASSERT_EQ(std::get<0>(*++citerable.begin()), entity[0u]); - ASSERT_EQ(cnt, std::size_t{0}); + static_assert(std::is_same_v(*iterable.begin())), int &>); + static_assert(std::is_same_v(*citerable.begin())), const char &>); + + // do not use iterable, make sure an iterable view works when created from a temporary + for(auto [entt, ivalue, cvalue]: registry.view().each()) { + ASSERT_EQ(entt::to_integral(entt), ivalue); + ASSERT_EQ(entt::to_integral(entt), cvalue); + } } -TEST(MultipleComponentView, EachWithType) { +TEST(MultiComponentView, EachWithSuggestedType) { entt::registry registry; for(auto i = 0; i < 3; ++i) { const auto entity = registry.create(); - registry.assign(entity, i); - registry.assign(entity); + registry.emplace(entity, i); + registry.emplace(entity); } // makes char a better candidate during iterations const auto entity = registry.create(); - registry.assign(entity, 99); + registry.emplace(entity, 99); - registry.view().each([value = 2](const auto curr, const auto) mutable { + registry.view().use().each([value = 2](const auto curr, const auto) mutable { ASSERT_EQ(curr, value--); }); @@ -374,81 +736,119 @@ TEST(MultipleComponentView, EachWithType) { return lhs < rhs; }); - registry.view().each([value = 0](const auto curr, const auto) mutable { + registry.view().use<0u>().each([value = 0](const auto curr, const auto) mutable { ASSERT_EQ(curr, value++); }); + + registry.sort([](const auto lhs, const auto rhs) { + return lhs > rhs; + }); + + auto value = registry.view().size_hint(); + + for(auto &&curr: registry.view().each()) { + ASSERT_EQ(std::get<1>(curr), static_cast(--value)); + } + + registry.sort([](const auto lhs, const auto rhs) { + return lhs < rhs; + }); + + value = {}; + + for(auto &&curr: registry.view().use().each()) { + ASSERT_EQ(std::get<1>(curr), static_cast(value++)); + } } -TEST(MultipleComponentView, EachWithHoles) { +TEST(MultiComponentView, EachWithHoles) { entt::registry registry; const auto e0 = registry.create(); const auto e1 = registry.create(); const auto e2 = registry.create(); - registry.assign(e0, '0'); - registry.assign(e1, '1'); + registry.emplace(e0, '0'); + registry.emplace(e1, '1'); - registry.assign(e0, 0); - registry.assign(e2, 2); + registry.emplace(e0, 0); + registry.emplace(e2, 2); auto view = registry.view(); view.each([e0](auto entity, const char &c, const int &i) { - if(e0 == entity) { - ASSERT_EQ(c, '0'); - ASSERT_EQ(i, 0); - } else { - FAIL(); - } + ASSERT_EQ(entity, e0); + ASSERT_EQ(c, '0'); + ASSERT_EQ(i, 0); }); + + for(auto &&curr: view.each()) { + ASSERT_EQ(std::get<0>(curr), e0); + ASSERT_EQ(std::get<1>(curr), '0'); + ASSERT_EQ(std::get<2>(curr), 0); + } } -TEST(MultipleComponentView, ConstNonConstAndAllInBetween) { +TEST(MultiComponentView, ConstNonConstAndAllInBetween) { entt::registry registry; - auto view = registry.view(); + auto view = registry.view(); - ASSERT_EQ(view.size(), decltype(view.size()){0}); + ASSERT_EQ(view.size_hint(), 0u); const auto entity = registry.create(); - registry.assign(entity, 0); - registry.assign(entity, 'c'); + registry.emplace(entity, 0); + registry.emplace(entity); + registry.emplace(entity, 'c'); + + ASSERT_EQ(view.size_hint(), 1u); + + static_assert(std::is_same_v({})), int &>); + static_assert(std::is_same_v({})), const char &>); + static_assert(std::is_same_v({})), std::tuple>); - ASSERT_EQ(view.size(), decltype(view.size()){1}); + static_assert(std::is_same_v({})), int &>); + static_assert(std::is_same_v({})), const char &>); + static_assert(std::is_same_v({})), std::tuple>); - ASSERT_TRUE((std::is_same_v(0)), int &>)); - ASSERT_TRUE((std::is_same_v(0)), const char &>)); - ASSERT_TRUE((std::is_same_v(0)), std::tuple>)); - ASSERT_TRUE((std::is_same_v()), const char *>)); - ASSERT_TRUE((std::is_same_v()), int *>)); + static_assert(std::is_same_v>); - view.each([](auto, auto &&i, auto &&c) { - ASSERT_TRUE((std::is_same_v)); - ASSERT_TRUE((std::is_same_v)); + static_assert(std::is_same_v()), decltype(std::as_const(registry).view())>); + static_assert(std::is_same_v()), decltype(std::as_const(registry).view())>); + static_assert(std::is_same_v()), decltype(std::as_const(registry).view())>); + + view.each([](auto &&i, auto &&c) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); }); + + for(auto [entt, iv, cv]: view.each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + } } -TEST(MultipleComponentView, Find) { +TEST(MultiComponentView, Find) { entt::registry registry; auto view = registry.view(); const auto e0 = registry.create(); - registry.assign(e0); - registry.assign(e0); + registry.emplace(e0); + registry.emplace(e0); const auto e1 = registry.create(); - registry.assign(e1); - registry.assign(e1); + registry.emplace(e1); + registry.emplace(e1); const auto e2 = registry.create(); - registry.assign(e2); - registry.assign(e2); + registry.emplace(e2); + registry.emplace(e2); const auto e3 = registry.create(); - registry.assign(e3); - registry.assign(e3); + registry.emplace(e3); + registry.emplace(e3); - registry.remove(e1); + registry.erase(e1); ASSERT_NE(view.find(e0), view.end()); ASSERT_EQ(view.find(e1), view.end()); @@ -466,41 +866,381 @@ TEST(MultipleComponentView, Find) { const auto e4 = registry.create(); registry.destroy(e4); const auto e5 = registry.create(); - registry.assign(e5); - registry.assign(e5); + registry.emplace(e5); + registry.emplace(e5); ASSERT_NE(view.find(e5), view.end()); ASSERT_EQ(view.find(e4), view.end()); } -TEST(MultiComponentView, Less) { +TEST(MultiComponentView, ExcludedComponents) { entt::registry registry; - const auto entity = std::get<0>(registry.create>()); - registry.create(); - registry.view>().less([entity](const auto entt, int, char) { + const auto e0 = registry.create(); + registry.emplace(e0, 0); + + const auto e1 = registry.create(); + registry.emplace(e1, 1); + registry.emplace(e1); + + const auto e2 = registry.create(); + registry.emplace(e2, 2); + + const auto e3 = registry.create(); + registry.emplace(e3, 3); + registry.emplace(e3); + + const auto view = std::as_const(registry).view(entt::exclude); + + for(const auto entity: view) { + ASSERT_TRUE(entity == e0 || entity == e2); + + if(entity == e0) { + ASSERT_EQ(view.get(e0), 0); + ASSERT_EQ(view.get<0u>(e0), 0); + } else if(entity == e2) { + ASSERT_EQ(std::get<0>(view.get(e2)), 2); + } + } + + registry.emplace(e0); + registry.emplace(e2); + registry.erase(e1); + registry.erase(e3); + + for(const auto entity: view) { + ASSERT_TRUE(entity == e1 || entity == e3); + + if(entity == e1) { + ASSERT_EQ(std::get<0>(view.get(e1)), 1); + } else if(entity == e3) { + ASSERT_EQ(view.get(e3), 3); + ASSERT_EQ(view.get<0u>(e3), 3); + } + } +} + +TEST(MultiComponentView, EmptyTypes) { + entt::registry registry; + + const auto entity = registry.create(); + registry.emplace(entity); + registry.emplace(entity); + registry.emplace(entity); + + const auto other = registry.create(); + registry.emplace(other); + registry.emplace(other); + registry.emplace(other); + registry.emplace(other); + + const auto ignored = registry.create(); + registry.emplace(ignored); + registry.emplace(ignored); + + registry.view(entt::exclude).each([entity](const auto entt, int, char) { ASSERT_EQ(entity, entt); }); - registry.view, char>().less([check = true](int, char) mutable { + for(auto [entt, iv, cv]: registry.view(entt::exclude).each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_EQ(entity, entt); + } + + registry.view(entt::exclude).each([check = true](int, char) mutable { ASSERT_TRUE(check); check = false; }); - registry.view, int, char>().less([entity](const auto entt, int, char) { + for(auto [entt, iv, cv]: registry.view(entt::exclude).each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_EQ(entity, entt); + } + + registry.view(entt::exclude).each([entity](const auto entt, int, char) { ASSERT_EQ(entity, entt); }); - registry.view, int, char>().less>([entity](const auto entt, int, char) { + for(auto [entt, iv, cv]: registry.view(entt::exclude).each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_EQ(entity, entt); + } + + registry.view(entt::exclude).use().each([entity](const auto entt, int, char) { ASSERT_EQ(entity, entt); }); - registry.view, char>().less>([check = true](int, char) mutable { + for(auto [entt, iv, cv]: registry.view(entt::exclude).use<0u>().each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_EQ(entity, entt); + } + + registry.view(entt::exclude).use<1u>().each([check = true](int, char) mutable { ASSERT_TRUE(check); check = false; }); - registry.view().less([entity](const auto entt, int, char, double) { + for(auto [entt, iv, cv]: registry.view(entt::exclude).use().each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); ASSERT_EQ(entity, entt); + } +} + +TEST(MultiComponentView, FrontBack) { + entt::registry registry; + auto view = registry.view(); + + ASSERT_EQ(view.front(), static_cast(entt::null)); + ASSERT_EQ(view.back(), static_cast(entt::null)); + + const auto e0 = registry.create(); + registry.emplace(e0); + registry.emplace(e0); + + const auto e1 = registry.create(); + registry.emplace(e1); + registry.emplace(e1); + + const auto entity = registry.create(); + registry.emplace(entity); + + ASSERT_EQ(view.front(), e1); + ASSERT_EQ(view.back(), e0); +} + +TEST(MultiComponentView, ExtendedGet) { + using type = decltype(std::declval().view().get({})); + static_assert(std::tuple_size_v == 2u); + static_assert(std::is_same_v, int &>); + static_assert(std::is_same_v, char &>); +} + +TEST(MultiComponentView, DeductionGuide) { + entt::registry registry; + typename entt::storage_traits::storage_type istorage; + typename entt::storage_traits::storage_type dstorage; + typename entt::storage_traits::storage_type sstorage; + + static_assert(std::is_same_v, entt::exclude_t<>>, decltype(entt::basic_view{istorage, dstorage})>); + static_assert(std::is_same_v, entt::exclude_t<>>, decltype(entt::basic_view{std::as_const(istorage), dstorage})>); + static_assert(std::is_same_v, entt::exclude_t<>>, decltype(entt::basic_view{istorage, std::as_const(dstorage)})>); + static_assert(std::is_same_v, entt::exclude_t<>>, decltype(entt::basic_view{std::as_const(istorage), std::as_const(dstorage)})>); + static_assert(std::is_same_v, entt::exclude_t<>>, decltype(entt::basic_view{istorage, sstorage})>); +} + +TEST(MultiComponentView, IterableViewAlgorithmCompatibility) { + entt::registry registry; + const auto entity = registry.create(); + + registry.emplace(entity); + registry.emplace(entity); + + const auto view = registry.view(); + const auto iterable = view.each(); + const auto it = std::find_if(iterable.begin(), iterable.end(), [entity](auto args) { return std::get<0>(args) == entity; }); + + ASSERT_EQ(std::get<0>(*it), entity); +} + +TEST(MultiComponentView, StableType) { + entt::registry registry; + auto view = registry.view(); + + const auto entity = registry.create(); + const auto other = registry.create(); + + registry.emplace(entity); + registry.emplace(other); + registry.emplace(entity); + registry.emplace(other); + registry.destroy(entity); + + ASSERT_EQ(view.size_hint(), 1u); + + view = view.use(); + + ASSERT_EQ(view.size_hint(), 2u); + ASSERT_FALSE(view.contains(entity)); + ASSERT_TRUE(view.contains(other)); + + ASSERT_EQ(view.front(), other); + ASSERT_EQ(view.back(), other); + + ASSERT_EQ(*view.begin(), other); + ASSERT_EQ(++view.begin(), view.end()); + + view.each([other](const auto entt, int, stable_type) { + ASSERT_EQ(other, entt); }); + + view.each([check = true](int, stable_type) mutable { + ASSERT_TRUE(check); + check = false; + }); + + for(auto [entt, iv, st]: view.each()) { + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + ASSERT_EQ(other, entt); + } + + registry.compact(); + + ASSERT_EQ(view.size_hint(), 1u); +} + +TEST(MultiComponentView, StableTypeWithExcludedComponent) { + entt::registry registry; + auto view = registry.view(entt::exclude).use(); + + const auto entity = registry.create(); + const auto other = registry.create(); + + registry.emplace(entity, 0); + registry.emplace(other, 42); + registry.emplace(entity); + + ASSERT_EQ(view.size_hint(), 2u); + ASSERT_FALSE(view.contains(entity)); + ASSERT_TRUE(view.contains(other)); + + registry.destroy(entity); + + ASSERT_EQ(view.size_hint(), 2u); + ASSERT_FALSE(view.contains(entity)); + ASSERT_TRUE(view.contains(other)); + + for(auto entt: view) { + constexpr entt::entity tombstone = entt::tombstone; + ASSERT_NE(entt, tombstone); + ASSERT_EQ(entt, other); + } + + for(auto [entt, comp]: view.each()) { + constexpr entt::entity tombstone = entt::tombstone; + ASSERT_NE(entt, tombstone); + ASSERT_EQ(entt, other); + ASSERT_EQ(comp.value, 42); + } + + view.each([other](const auto entt, auto &&...) { + constexpr entt::entity tombstone = entt::tombstone; + ASSERT_NE(entt, tombstone); + ASSERT_EQ(entt, other); + }); +} + +TEST(MultiComponentView, SameComponentTypes) { + entt::registry registry; + typename entt::storage_traits::storage_type storage; + typename entt::storage_traits::storage_type other; + entt::basic_view view{storage, other}; + + storage.bind(entt::forward_as_any(registry)); + other.bind(entt::forward_as_any(registry)); + + const entt::entity e0{42u}; + const entt::entity e1{3u}; + + storage.emplace(e0, 7); + other.emplace(e0, 9); + other.emplace(e1, 1); + + ASSERT_TRUE(view.contains(e0)); + ASSERT_FALSE(view.contains(e1)); + + ASSERT_EQ((view.get<0u, 1u>(e0)), (std::make_tuple(7, 9))); + ASSERT_EQ(view.get<1u>(e0), 9); + + for(auto entt: view) { + ASSERT_EQ(entt, e0); + } + + view.each([&](auto entt, auto &&first, auto &&second) { + ASSERT_EQ(entt, e0); + ASSERT_EQ(first, 7); + ASSERT_EQ(second, 9); + }); + + for(auto [entt, first, second]: view.each()) { + ASSERT_EQ(entt, e0); + ASSERT_EQ(first, 7); + ASSERT_EQ(second, 9); + } + + ASSERT_EQ(&view.handle(), &storage); + ASSERT_EQ(&view.use<1u>().handle(), &other); +} + +TEST(View, Pipe) { + entt::registry registry; + const auto entity = registry.create(); + const auto other = registry.create(); + + registry.emplace(entity); + registry.emplace(entity); + registry.emplace(entity); + registry.emplace(entity); + + registry.emplace(other); + registry.emplace(other); + registry.emplace(other); + + const auto view1 = registry.view(entt::exclude); + const auto view2 = registry.view(entt::exclude); + const auto view3 = registry.view(); + const auto view4 = registry.view(); + + static_assert(std::is_same_v, entt::exclude_t>, decltype(view1 | view2)>); + static_assert(std::is_same_v, entt::exclude_t>, decltype(view2 | view1)>); + static_assert(std::is_same_v); + + ASSERT_FALSE((view1 | view2).contains(entity)); + ASSERT_TRUE((view1 | view2).contains(other)); + + ASSERT_TRUE((view3 | view2).contains(entity)); + ASSERT_FALSE((view3 | view2).contains(other)); + + ASSERT_FALSE((view1 | view2 | view3).contains(entity)); + ASSERT_FALSE((view1 | view2 | view3).contains(other)); + + ASSERT_FALSE((view1 | view4 | view2).contains(entity)); + ASSERT_TRUE((view1 | view4 | view2).contains(other)); +} + +TEST(MultiComponentView, Storage) { + entt::registry registry; + const auto entity = registry.create(); + const auto view = registry.view(); + + static_assert(std::is_same_v()), typename entt::storage_traits::storage_type &>); + static_assert(std::is_same_v()), typename entt::storage_traits::storage_type &>); + static_assert(std::is_same_v()), const typename entt::storage_traits::storage_type &>); + static_assert(std::is_same_v()), const typename entt::storage_traits::storage_type &>); + + ASSERT_EQ(view.size_hint(), 0u); + + view.storage().emplace(entity); + registry.emplace(entity); + + ASSERT_EQ(view.size_hint(), 1u); + ASSERT_TRUE(view.storage().contains(entity)); + ASSERT_TRUE((registry.all_of(entity))); + + view.storage<0u>().erase(entity); + + ASSERT_EQ(view.size_hint(), 0u); + ASSERT_TRUE(view.storage<1u>().contains(entity)); + ASSERT_FALSE((registry.all_of(entity))); } diff --git a/modules/entt/test/entt/locator/locator.cpp b/modules/entt/test/entt/locator/locator.cpp index 53eb565..1e226b1 100644 --- a/modules/entt/test/entt/locator/locator.cpp +++ b/modules/entt/test/entt/locator/locator.cpp @@ -1,48 +1,68 @@ +#include #include #include -struct a_service {}; +struct base_service { + virtual ~base_service() = default; + virtual void invoke() {} +}; + +struct null_service: base_service { + void invoke() override { + invoked = true; + } -struct another_service { - virtual ~another_service() = default; - virtual void f(bool) = 0; - bool check{false}; + static inline bool invoked{}; }; -struct derived_service: another_service { - derived_service(int): another_service{} {} - void f(bool b) override { check = b; } +struct derived_service: base_service { + void invoke() override { + invoked = true; + } + + static inline bool invoked{}; }; -TEST(ServiceLocator, Functionalities) { - ASSERT_TRUE(entt::service_locator::empty()); - ASSERT_TRUE(entt::service_locator::empty()); +struct ServiceLocator: ::testing::Test { + void SetUp() override { + null_service::invoked = false; + derived_service::invoked = false; + } +}; + +using ServiceLocatorDeathTest = ServiceLocator; - entt::service_locator::set(); +TEST(ServiceLocator, Functionalities) { + ASSERT_FALSE(entt::locator::has_value()); + ASSERT_FALSE(null_service::invoked); - ASSERT_FALSE(entt::service_locator::empty()); - ASSERT_TRUE(entt::service_locator::empty()); + entt::locator::value_or().invoke(); - entt::service_locator::reset(); + ASSERT_TRUE(entt::locator::has_value()); + ASSERT_TRUE(null_service::invoked); - ASSERT_TRUE(entt::service_locator::empty()); - ASSERT_TRUE(entt::service_locator::empty()); + entt::locator::reset(); - entt::service_locator::set(std::make_shared()); + ASSERT_FALSE(entt::locator::has_value()); + ASSERT_FALSE(derived_service::invoked); - ASSERT_FALSE(entt::service_locator::empty()); - ASSERT_TRUE(entt::service_locator::empty()); + entt::locator::emplace(); + entt::locator::value().invoke(); - entt::service_locator::set(42); + ASSERT_TRUE(entt::locator::has_value()); + ASSERT_TRUE(derived_service::invoked); - ASSERT_FALSE(entt::service_locator::empty()); - ASSERT_FALSE(entt::service_locator::empty()); + derived_service::invoked = false; + entt::locator::allocate_emplace(std::allocator{}).invoke(); - entt::service_locator::get().lock()->f(!entt::service_locator::get().lock()->check); + ASSERT_TRUE(entt::locator::has_value()); + ASSERT_TRUE(derived_service::invoked); +} - ASSERT_TRUE(entt::service_locator::get().lock()->check); +TEST(ServiceLocatorDeathTest, UninitializedValue) { + ASSERT_NO_FATAL_FAILURE(entt::locator::value_or().invoke()); - entt::service_locator::ref().f(!entt::service_locator::get().lock()->check); + entt::locator::reset(); - ASSERT_FALSE(entt::service_locator::get().lock()->check); + ASSERT_DEATH(entt::locator::value().invoke(), ""); } diff --git a/modules/entt/test/entt/meta/meta.cpp b/modules/entt/test/entt/meta/meta.cpp deleted file mode 100644 index 0098ac3..0000000 --- a/modules/entt/test/entt/meta/meta.cpp +++ /dev/null @@ -1,1627 +0,0 @@ -#include -#include -#include -#include -#include -#include - -enum class properties { - prop_int, - prop_bool -}; - -struct empty_type { - virtual ~empty_type() = default; - - static void destroy(empty_type *) { - ++counter; - } - - inline static int counter = 0; -}; - -struct fat_type: empty_type { - fat_type() = default; - - fat_type(int *value) - : foo{value}, bar{value} - {} - - int *foo{nullptr}; - int *bar{nullptr}; - - bool operator==(const fat_type &other) const { - return foo == other.foo && bar == other.bar; - } -}; - -union union_type { - int i; - double d; -}; - -bool operator!=(const fat_type &lhs, const fat_type &rhs) { - return !(lhs == rhs); -} - -struct base_type { - virtual ~base_type() = default; -}; - -struct derived_type: base_type { - derived_type() = default; - - derived_type(const base_type &, int value, char character) - : i{value}, c{character} - {} - - const int i{}; - const char c{}; -}; - -derived_type derived_factory(const base_type &, int value) { - return {derived_type{}, value, 'c'}; -} - -struct data_type { - int i{0}; - const int j{1}; - inline static int h{2}; - inline static const int k{3}; - empty_type empty{}; -}; - -struct array_type { - static inline int global[3]; - int local[3]; -}; - -struct func_type { - int f(const base_type &, int a, int b) { return f(a, b); } - int f(int a, int b) { value = a; return b*b; } - int f(int v) const { return v*v; } - void g(int v) { value = v*v; } - - static int h(int v) { return v; } - static void k(int v) { value = v; } - - inline static int value = 0; -}; - -struct setter_getter_type { - int value{}; - - int setter(int val) { return value = val; } - int getter() { return value; } - - int setter_with_ref(const int &val) { return value = val; } - const int & getter_with_ref() { return value; } - - static int static_setter(setter_getter_type *type, int value) { return type->value = value; } - static int static_getter(const setter_getter_type *type) { return type->value; } -}; - -struct not_comparable_type { - bool operator==(const not_comparable_type &) const = delete; -}; - -bool operator!=(const not_comparable_type &, const not_comparable_type &) = delete; - -struct an_abstract_type { - virtual ~an_abstract_type() = default; - void f(int v) { i = v; } - virtual void g(int) = 0; - int i{}; -}; - -struct another_abstract_type { - virtual ~another_abstract_type() = default; - virtual void h(char) = 0; - char j{}; -}; - -struct concrete_type: an_abstract_type, another_abstract_type { - void f(int v) { i = v*v; } // hide, it's ok :-) - void g(int v) override { i = -v; } - void h(char c) override { j = c; } -}; - -struct Meta: public ::testing::Test { - static void SetUpTestCase() { - entt::reflect().conv(); - - entt::reflect("char", std::make_pair(properties::prop_int, 42)); - - entt::reflect() - .data("prop_bool") - .data("prop_int"); - - entt::reflect().data<0u>("min").data<100u>("max"); - - entt::reflect("base"); - - entt::reflect("derived", std::make_pair(properties::prop_int, 99)) - .base() - .ctor(std::make_pair(properties::prop_bool, false)) - .ctor<&derived_factory>(std::make_pair(properties::prop_int, 42)); - - entt::reflect("empty") - .dtor<&empty_type::destroy>(); - - entt::reflect("fat") - .base() - .dtor<&fat_type::destroy>(); - - entt::reflect("data") - .data<&data_type::i>("i", std::make_pair(properties::prop_int, 0)) - .data<&data_type::j>("j", std::make_pair(properties::prop_int, 1)) - .data<&data_type::h>("h", std::make_pair(properties::prop_int, 2)) - .data<&data_type::k>("k", std::make_pair(properties::prop_int, 3)) - .data<&data_type::empty>("empty"); - - entt::reflect("array") - .data<&array_type::global>("global") - .data<&array_type::local>("local"); - - entt::reflect("func") - .func(&func_type::f)>("f3") - .func(&func_type::f)>("f2", std::make_pair(properties::prop_bool, false)) - .func(&func_type::f)>("f1", std::make_pair(properties::prop_bool, false)) - .func<&func_type::g>("g", std::make_pair(properties::prop_bool, false)) - .func<&func_type::h>("h", std::make_pair(properties::prop_bool, false)) - .func<&func_type::k>("k", std::make_pair(properties::prop_bool, false)); - - entt::reflect("setter_getter") - .data<&setter_getter_type::static_setter, &setter_getter_type::static_getter>("x") - .data<&setter_getter_type::setter, &setter_getter_type::getter>("y") - .data<&setter_getter_type::static_setter, &setter_getter_type::getter>("z") - .data<&setter_getter_type::setter_with_ref, &setter_getter_type::getter_with_ref>("w"); - - entt::reflect("an_abstract_type", std::make_pair(properties::prop_bool, false)) - .data<&an_abstract_type::i>("i") - .func<&an_abstract_type::f>("f") - .func<&an_abstract_type::g>("g"); - - entt::reflect("another_abstract_type", std::make_pair(properties::prop_int, 42)) - .data<&another_abstract_type::j>("j") - .func<&another_abstract_type::h>("h"); - - entt::reflect("concrete") - .base() - .base() - .func<&concrete_type::f>("f"); - } - - static void SetUpAfterUnregistration() { - entt::reflect().conv(); - - entt::reflect("my_type", std::make_pair(properties::prop_bool, false)) - .ctor<>(); - - entt::reflect("your_type") - .data<&another_abstract_type::j>("a_data_member") - .func<&another_abstract_type::h>("a_member_function"); - } - - void SetUp() override { - empty_type::counter = 0; - func_type::value = 0; - } -}; - -TEST_F(Meta, Resolve) { - ASSERT_EQ(entt::resolve(), entt::resolve("derived")); - - bool found = false; - - entt::resolve([&found](auto type) { - found = found || type == entt::resolve(); - }); - - ASSERT_TRUE(found); -} - -TEST_F(Meta, MetaAnySBO) { - entt::meta_any any{'c'}; - - ASSERT_TRUE(any); - ASSERT_FALSE(any.can_cast()); - ASSERT_TRUE(any.can_cast()); - ASSERT_EQ(any.cast(), 'c'); - ASSERT_EQ(std::as_const(any).cast(), 'c'); - ASSERT_NE(any.data(), nullptr); - ASSERT_NE(std::as_const(any).data(), nullptr); - ASSERT_EQ(any, entt::meta_any{'c'}); - ASSERT_NE(any, entt::meta_any{'h'}); -} - -TEST_F(Meta, MetaAnyNoSBO) { - int value = 42; - fat_type instance{&value}; - entt::meta_any any{instance}; - - ASSERT_TRUE(any); - ASSERT_FALSE(any.can_cast()); - ASSERT_TRUE(any.can_cast()); - ASSERT_EQ(any.cast(), instance); - ASSERT_EQ(std::as_const(any).cast(), instance); - ASSERT_NE(any.data(), nullptr); - ASSERT_NE(std::as_const(any).data(), nullptr); - ASSERT_EQ(any, entt::meta_any{instance}); - ASSERT_NE(any, fat_type{}); -} - -TEST_F(Meta, MetaAnyEmpty) { - entt::meta_any any{}; - - ASSERT_FALSE(any); - ASSERT_FALSE(any.type()); - ASSERT_FALSE(any.can_cast()); - ASSERT_FALSE(any.can_cast()); - ASSERT_EQ(any.data(), nullptr); - ASSERT_EQ(std::as_const(any).data(), nullptr); - ASSERT_EQ(any, entt::meta_any{}); - ASSERT_NE(any, entt::meta_any{'c'}); -} - -TEST_F(Meta, MetaAnySBOCopyConstruction) { - entt::meta_any any{42}; - entt::meta_any other{any}; - - ASSERT_TRUE(any); - ASSERT_TRUE(other); - ASSERT_FALSE(other.can_cast()); - ASSERT_TRUE(other.can_cast()); - ASSERT_EQ(other.cast(), 42); - ASSERT_EQ(std::as_const(other).cast(), 42); - ASSERT_EQ(other, entt::meta_any{42}); - ASSERT_NE(other, entt::meta_any{0}); -} - -TEST_F(Meta, MetaAnySBOCopyAssignment) { - entt::meta_any any{42}; - entt::meta_any other{}; - - other = any; - - ASSERT_TRUE(any); - ASSERT_TRUE(other); - ASSERT_FALSE(other.can_cast()); - ASSERT_TRUE(other.can_cast()); - ASSERT_EQ(other.cast(), 42); - ASSERT_EQ(std::as_const(other).cast(), 42); - ASSERT_EQ(other, entt::meta_any{42}); - ASSERT_NE(other, entt::meta_any{0}); -} - -TEST_F(Meta, MetaAnySBOMoveConstruction) { - entt::meta_any any{42}; - entt::meta_any other{std::move(any)}; - - ASSERT_FALSE(any); - ASSERT_TRUE(other); - ASSERT_FALSE(other.can_cast()); - ASSERT_TRUE(other.can_cast()); - ASSERT_EQ(other.cast(), 42); - ASSERT_EQ(std::as_const(other).cast(), 42); - ASSERT_EQ(other, entt::meta_any{42}); - ASSERT_NE(other, entt::meta_any{0}); -} - -TEST_F(Meta, MetaAnySBOMoveAssignment) { - entt::meta_any any{42}; - entt::meta_any other{}; - - other = std::move(any); - - ASSERT_FALSE(any); - ASSERT_TRUE(other); - ASSERT_FALSE(other.can_cast()); - ASSERT_TRUE(other.can_cast()); - ASSERT_EQ(other.cast(), 42); - ASSERT_EQ(std::as_const(other).cast(), 42); - ASSERT_EQ(other, entt::meta_any{42}); - ASSERT_NE(other, entt::meta_any{0}); -} - -TEST_F(Meta, MetaAnyNoSBOCopyConstruction) { - int value = 42; - fat_type instance{&value}; - entt::meta_any any{instance}; - entt::meta_any other{any}; - - ASSERT_TRUE(any); - ASSERT_TRUE(other); - ASSERT_FALSE(other.can_cast()); - ASSERT_TRUE(other.can_cast()); - ASSERT_EQ(other.cast(), instance); - ASSERT_EQ(std::as_const(other).cast(), instance); - ASSERT_EQ(other, entt::meta_any{instance}); - ASSERT_NE(other, fat_type{}); -} - -TEST_F(Meta, MetaAnyNoSBOCopyAssignment) { - int value = 42; - fat_type instance{&value}; - entt::meta_any any{instance}; - entt::meta_any other{}; - - other = any; - - ASSERT_TRUE(any); - ASSERT_TRUE(other); - ASSERT_FALSE(other.can_cast()); - ASSERT_TRUE(other.can_cast()); - ASSERT_EQ(other.cast(), instance); - ASSERT_EQ(std::as_const(other).cast(), instance); - ASSERT_EQ(other, entt::meta_any{instance}); - ASSERT_NE(other, fat_type{}); -} - -TEST_F(Meta, MetaAnyNoSBOMoveConstruction) { - int value = 42; - fat_type instance{&value}; - entt::meta_any any{instance}; - entt::meta_any other{std::move(any)}; - - ASSERT_FALSE(any); - ASSERT_TRUE(other); - ASSERT_FALSE(other.can_cast()); - ASSERT_TRUE(other.can_cast()); - ASSERT_EQ(other.cast(), instance); - ASSERT_EQ(std::as_const(other).cast(), instance); - ASSERT_EQ(other, entt::meta_any{instance}); - ASSERT_NE(other, fat_type{}); -} - -TEST_F(Meta, MetaAnyNoSBOMoveAssignment) { - int value = 42; - fat_type instance{&value}; - entt::meta_any any{instance}; - entt::meta_any other{}; - - other = std::move(any); - - ASSERT_FALSE(any); - ASSERT_TRUE(other); - ASSERT_FALSE(other.can_cast()); - ASSERT_TRUE(other.can_cast()); - ASSERT_EQ(other.cast(), instance); - ASSERT_EQ(std::as_const(other).cast(), instance); - ASSERT_EQ(other, entt::meta_any{instance}); - ASSERT_NE(other, fat_type{}); -} - -TEST_F(Meta, MetaAnySBODestruction) { - ASSERT_EQ(empty_type::counter, 0); - { entt::meta_any any{empty_type{}}; } - ASSERT_EQ(empty_type::counter, 1); -} - -TEST_F(Meta, MetaAnyNoSBODestruction) { - ASSERT_EQ(fat_type::counter, 0); - { entt::meta_any any{fat_type{}}; } - ASSERT_EQ(fat_type::counter, 1); -} - -TEST_F(Meta, MetaAnySBOSwap) { - entt::meta_any lhs{'c'}; - entt::meta_any rhs{42}; - - std::swap(lhs, rhs); - - ASSERT_TRUE(lhs.can_cast()); - ASSERT_EQ(lhs.cast(), 42); - ASSERT_TRUE(rhs.can_cast()); - ASSERT_EQ(rhs.cast(), 'c'); -} - -TEST_F(Meta, MetaAnyNoSBOSwap) { - int i, j; - entt::meta_any lhs{fat_type{&i}}; - entt::meta_any rhs{fat_type{&j}}; - - std::swap(lhs, rhs); - - ASSERT_EQ(lhs.cast().foo, &j); - ASSERT_EQ(rhs.cast().bar, &i); -} - -TEST_F(Meta, MetaAnySBOWithNoSBOSwap) { - int value = 42; - entt::meta_any lhs{fat_type{&value}}; - entt::meta_any rhs{'c'}; - - std::swap(lhs, rhs); - - ASSERT_TRUE(lhs.can_cast()); - ASSERT_EQ(lhs.cast(), 'c'); - ASSERT_TRUE(rhs.can_cast()); - ASSERT_EQ(rhs.cast().foo, &value); - ASSERT_EQ(rhs.cast().bar, &value); -} - -TEST_F(Meta, MetaAnySBOWithEmptySwap) { - entt::meta_any lhs{'c'}; - entt::meta_any rhs{}; - - std::swap(lhs, rhs); - - ASSERT_FALSE(lhs); - ASSERT_TRUE(rhs.can_cast()); - ASSERT_EQ(rhs.cast(), 'c'); - - std::swap(lhs, rhs); - - ASSERT_FALSE(rhs); - ASSERT_TRUE(lhs.can_cast()); - ASSERT_EQ(lhs.cast(), 'c'); -} - -TEST_F(Meta, MetaAnyNoSBOWithEmptySwap) { - int i; - entt::meta_any lhs{fat_type{&i}}; - entt::meta_any rhs{}; - - std::swap(lhs, rhs); - - ASSERT_EQ(rhs.cast().bar, &i); - - std::swap(lhs, rhs); - - ASSERT_EQ(lhs.cast().bar, &i); -} - -TEST_F(Meta, MetaAnyComparable) { - entt::meta_any any{'c'}; - - ASSERT_EQ(any, any); - ASSERT_EQ(any, entt::meta_any{'c'}); - ASSERT_NE(any, entt::meta_any{'a'}); - ASSERT_NE(any, entt::meta_any{}); - - ASSERT_TRUE(any == any); - ASSERT_TRUE(any == entt::meta_any{'c'}); - ASSERT_FALSE(any == entt::meta_any{'a'}); - ASSERT_TRUE(any != entt::meta_any{'a'}); - ASSERT_TRUE(any != entt::meta_any{}); -} - -TEST_F(Meta, MetaAnyNotComparable) { - entt::meta_any any{not_comparable_type{}}; - - ASSERT_EQ(any, any); - ASSERT_NE(any, entt::meta_any{not_comparable_type{}}); - ASSERT_NE(any, entt::meta_any{}); - - ASSERT_TRUE(any == any); - ASSERT_FALSE(any == entt::meta_any{not_comparable_type{}}); - ASSERT_TRUE(any != entt::meta_any{}); -} - -TEST_F(Meta, MetaAnyCast) { - entt::meta_any any{derived_type{}}; - entt::meta_handle handle{any}; - - ASSERT_TRUE(any); - ASSERT_EQ(any.type(), entt::resolve()); - ASSERT_FALSE(any.can_cast()); - ASSERT_TRUE(any.can_cast()); - ASSERT_TRUE(any.can_cast()); - ASSERT_EQ(&any.cast(), handle.try_cast()); - ASSERT_EQ(&any.cast(), handle.try_cast()); - ASSERT_EQ(&std::as_const(any).cast(), handle.try_cast()); - ASSERT_EQ(&std::as_const(any).cast(), handle.try_cast()); -} - -TEST_F(Meta, MetaAnyConvert) { - entt::meta_any any{42.}; - - ASSERT_TRUE(any); - ASSERT_EQ(any.type(), entt::resolve()); - ASSERT_FALSE(any.can_convert()); - ASSERT_TRUE(any.can_convert()); - ASSERT_TRUE(any.can_convert()); - - ASSERT_TRUE(any.convert()); - ASSERT_FALSE(any.convert()); - - ASSERT_EQ(any.type(), entt::resolve()); - ASSERT_EQ(any.cast(), 42.); - - ASSERT_TRUE(any.convert()); - - ASSERT_EQ(any.type(), entt::resolve()); - ASSERT_EQ(any.cast(), 42); -} - -TEST_F(Meta, MetaAnyConstConvert) { - const entt::meta_any any{42.}; - - ASSERT_TRUE(any); - ASSERT_EQ(any.type(), entt::resolve()); - ASSERT_FALSE(any.can_convert()); - ASSERT_TRUE(any.can_convert()); - ASSERT_TRUE(any.can_convert()); - - ASSERT_TRUE(any.convert()); - ASSERT_FALSE(any.convert()); - - ASSERT_EQ(any.type(), entt::resolve()); - ASSERT_EQ(any.cast(), 42.); - - auto other = any.convert(); - - ASSERT_EQ(any.type(), entt::resolve()); - ASSERT_EQ(any.cast(), 42.); - ASSERT_EQ(other.type(), entt::resolve()); - ASSERT_EQ(other.cast(), 42); -} - -TEST_F(Meta, MetaHandleFromObject) { - empty_type empty{}; - entt::meta_handle handle{empty}; - - ASSERT_TRUE(handle); - ASSERT_EQ(handle.type(), entt::resolve()); - ASSERT_EQ(handle.try_cast(), nullptr); - ASSERT_EQ(handle.try_cast(), &empty); - ASSERT_EQ(std::as_const(handle).try_cast(), &empty); - ASSERT_EQ(handle.data(), &empty); - ASSERT_EQ(std::as_const(handle).data(), &empty); -} - -TEST_F(Meta, MetaHandleFromMetaAny) { - entt::meta_any any{42}; - entt::meta_handle handle{any}; - - ASSERT_TRUE(handle); - ASSERT_EQ(handle.type(), entt::resolve()); - ASSERT_EQ(handle.try_cast(), nullptr); - ASSERT_EQ(handle.try_cast(), any.data()); - ASSERT_EQ(std::as_const(handle).try_cast(), any.data()); - ASSERT_EQ(handle.data(), any.data()); - ASSERT_EQ(std::as_const(handle).data(), any.data()); -} - -TEST_F(Meta, MetaHandleEmpty) { - entt::meta_handle handle{}; - - ASSERT_FALSE(handle); - ASSERT_FALSE(handle.type()); - ASSERT_EQ(handle.try_cast(), nullptr); - ASSERT_EQ(handle.try_cast(), nullptr); - ASSERT_EQ(handle.data(), nullptr); - ASSERT_EQ(std::as_const(handle).data(), nullptr); -} - -TEST_F(Meta, MetaHandleTryCast) { - derived_type derived{}; - base_type *base = &derived; - entt::meta_handle handle{derived}; - - ASSERT_TRUE(handle); - ASSERT_EQ(handle.type(), entt::resolve()); - ASSERT_EQ(handle.try_cast(), nullptr); - ASSERT_EQ(handle.try_cast(), base); - ASSERT_EQ(handle.try_cast(), &derived); - ASSERT_EQ(std::as_const(handle).try_cast(), base); - ASSERT_EQ(std::as_const(handle).try_cast(), &derived); - ASSERT_EQ(handle.data(), &derived); - ASSERT_EQ(std::as_const(handle).data(), &derived); -} - -TEST_F(Meta, MetaProp) { - auto prop = entt::resolve().prop(properties::prop_int); - - ASSERT_TRUE(prop); - ASSERT_NE(prop, entt::meta_prop{}); - ASSERT_EQ(prop.key(), properties::prop_int); - ASSERT_EQ(prop.value(), 42); -} - -TEST_F(Meta, MetaBase) { - auto base = entt::resolve().base("base"); - derived_type derived{}; - - ASSERT_TRUE(base); - ASSERT_NE(base, entt::meta_base{}); - ASSERT_EQ(base.parent(), entt::resolve("derived")); - ASSERT_EQ(base.type(), entt::resolve()); - ASSERT_EQ(base.cast(&derived), static_cast(&derived)); -} - -TEST_F(Meta, MetaConv) { - auto conv = entt::resolve().conv(); - double value = 3.; - - ASSERT_TRUE(conv); - ASSERT_NE(conv, entt::meta_conv{}); - ASSERT_EQ(conv.parent(), entt::resolve()); - ASSERT_EQ(conv.type(), entt::resolve()); - - auto any = conv.convert(&value); - - ASSERT_TRUE(any); - ASSERT_EQ(any.type(), entt::resolve()); - ASSERT_EQ(any.cast(), 3); -} - -TEST_F(Meta, MetaCtor) { - auto ctor = entt::resolve().ctor(); - - ASSERT_TRUE(ctor); - ASSERT_NE(ctor, entt::meta_ctor{}); - ASSERT_EQ(ctor.parent(), entt::resolve("derived")); - ASSERT_EQ(ctor.size(), entt::meta_ctor::size_type{3}); - ASSERT_EQ(ctor.arg(entt::meta_ctor::size_type{0}), entt::resolve()); - ASSERT_EQ(ctor.arg(entt::meta_ctor::size_type{1}), entt::resolve()); - ASSERT_EQ(ctor.arg(entt::meta_ctor::size_type{2}), entt::resolve()); - ASSERT_FALSE(ctor.arg(entt::meta_ctor::size_type{3})); - - auto any = ctor.invoke(base_type{}, 42, 'c'); - auto empty = ctor.invoke(); - - ASSERT_FALSE(empty); - ASSERT_TRUE(any); - ASSERT_TRUE(any.can_cast()); - ASSERT_EQ(any.cast().i, 42); - ASSERT_EQ(any.cast().c, 'c'); - - ctor.prop([](auto prop) { - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_bool); - ASSERT_FALSE(prop.value().template cast()); - }); - - ASSERT_FALSE(ctor.prop(properties::prop_int)); - - auto prop = ctor.prop(properties::prop_bool); - - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_bool); - ASSERT_FALSE(prop.value().template cast()); -} - -TEST_F(Meta, MetaCtorFunc) { - auto ctor = entt::resolve().ctor(); - - ASSERT_TRUE(ctor); - ASSERT_EQ(ctor.parent(), entt::resolve("derived")); - ASSERT_EQ(ctor.size(), entt::meta_ctor::size_type{2}); - ASSERT_EQ(ctor.arg(entt::meta_ctor::size_type{0}), entt::resolve()); - ASSERT_EQ(ctor.arg(entt::meta_ctor::size_type{1}), entt::resolve()); - ASSERT_FALSE(ctor.arg(entt::meta_ctor::size_type{2})); - - auto any = ctor.invoke(derived_type{}, 42); - auto empty = ctor.invoke(3, 'c'); - - ASSERT_FALSE(empty); - ASSERT_TRUE(any); - ASSERT_TRUE(any.can_cast()); - ASSERT_EQ(any.cast().i, 42); - ASSERT_EQ(any.cast().c, 'c'); - - ctor.prop([](auto prop) { - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_int); - ASSERT_EQ(prop.value(), 42); - }); - - ASSERT_FALSE(ctor.prop(properties::prop_bool)); - - auto prop = ctor.prop(properties::prop_int); - - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_int); - ASSERT_EQ(prop.value(), 42); -} - -TEST_F(Meta, MetaCtorMetaAnyArgs) { - auto ctor = entt::resolve().ctor(); - auto any = ctor.invoke(base_type{}, entt::meta_any{42}, entt::meta_any{'c'}); - - ASSERT_TRUE(any); - ASSERT_TRUE(any.can_cast()); - ASSERT_EQ(any.cast().i, 42); - ASSERT_EQ(any.cast().c, 'c'); -} - -TEST_F(Meta, MetaCtorInvalidArgs) { - auto ctor = entt::resolve().ctor(); - ASSERT_FALSE(ctor.invoke(base_type{}, entt::meta_any{'c'}, entt::meta_any{42})); -} - -TEST_F(Meta, MetaCtorCastAndConvert) { - auto ctor = entt::resolve().ctor(); - auto any = ctor.invoke(entt::meta_any{derived_type{}}, entt::meta_any{42.}, entt::meta_any{'c'}); - - ASSERT_TRUE(any); - ASSERT_TRUE(any.can_cast()); - ASSERT_EQ(any.cast().i, 42); - ASSERT_EQ(any.cast().c, 'c'); -} - -TEST_F(Meta, MetaCtorFuncMetaAnyArgs) { - auto ctor = entt::resolve().ctor(); - auto any = ctor.invoke(base_type{}, entt::meta_any{42}); - - ASSERT_TRUE(any); - ASSERT_TRUE(any.can_cast()); - ASSERT_EQ(any.cast().i, 42); - ASSERT_EQ(any.cast().c, 'c'); -} - -TEST_F(Meta, MetaCtorFuncInvalidArgs) { - auto ctor = entt::resolve().ctor(); - ASSERT_FALSE(ctor.invoke(base_type{}, entt::meta_any{'c'})); -} - -TEST_F(Meta, MetaCtorFuncCastAndConvert) { - auto ctor = entt::resolve().ctor(); - auto any = ctor.invoke(entt::meta_any{derived_type{}}, entt::meta_any{42.}); - - ASSERT_TRUE(any); - ASSERT_TRUE(any.can_cast()); - ASSERT_EQ(any.cast().i, 42); - ASSERT_EQ(any.cast().c, 'c'); -} - -TEST_F(Meta, MetaDtor) { - auto dtor = entt::resolve().dtor(); - empty_type empty{}; - - ASSERT_TRUE(dtor); - ASSERT_NE(dtor, entt::meta_dtor{}); - ASSERT_EQ(dtor.parent(), entt::resolve("empty")); - ASSERT_EQ(empty_type::counter, 0); - ASSERT_TRUE(dtor.invoke(empty)); - ASSERT_EQ(empty_type::counter, 1); -} - -TEST_F(Meta, MetaDtorMetaAnyArg) { - auto dtor = entt::resolve().dtor(); - entt::meta_any any{empty_type{}}; - - ASSERT_EQ(empty_type::counter, 0); - ASSERT_TRUE(dtor.invoke(any)); - ASSERT_EQ(empty_type::counter, 1); -} - -TEST_F(Meta, MetaDtorMetaAnyInvalidArg) { - ASSERT_FALSE(entt::resolve().dtor().invoke(int{})); -} - - -TEST_F(Meta, MetaData) { - auto data = entt::resolve().data("i"); - data_type instance{}; - - ASSERT_TRUE(data); - ASSERT_NE(data, entt::meta_data{}); - ASSERT_EQ(data.parent(), entt::resolve("data")); - ASSERT_EQ(data.type(), entt::resolve()); - ASSERT_STREQ(data.name(), "i"); - ASSERT_FALSE(data.is_const()); - ASSERT_FALSE(data.is_static()); - ASSERT_EQ(data.get(instance).cast(), 0); - ASSERT_TRUE(data.set(instance, 42)); - ASSERT_EQ(data.get(instance).cast(), 42); - - data.prop([](auto prop) { - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_int); - ASSERT_EQ(prop.value(), 0); - }); - - ASSERT_FALSE(data.prop(properties::prop_bool)); - - auto prop = data.prop(properties::prop_int); - - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_int); - ASSERT_EQ(prop.value(), 0); -} - -TEST_F(Meta, MetaDataConst) { - auto data = entt::resolve().data("j"); - data_type instance{}; - - ASSERT_TRUE(data); - ASSERT_EQ(data.parent(), entt::resolve("data")); - ASSERT_EQ(data.type(), entt::resolve()); - ASSERT_STREQ(data.name(), "j"); - ASSERT_TRUE(data.is_const()); - ASSERT_FALSE(data.is_static()); - ASSERT_EQ(data.get(instance).cast(), 1); - ASSERT_FALSE(data.set(instance, 42)); - ASSERT_EQ(data.get(instance).cast(), 1); - - data.prop([](auto prop) { - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_int); - ASSERT_EQ(prop.value(), 1); - }); - - ASSERT_FALSE(data.prop(properties::prop_bool)); - - auto prop = data.prop(properties::prop_int); - - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_int); - ASSERT_EQ(prop.value(), 1); -} - -TEST_F(Meta, MetaDataStatic) { - auto data = entt::resolve().data("h"); - - ASSERT_TRUE(data); - ASSERT_EQ(data.parent(), entt::resolve("data")); - ASSERT_EQ(data.type(), entt::resolve()); - ASSERT_STREQ(data.name(), "h"); - ASSERT_FALSE(data.is_const()); - ASSERT_TRUE(data.is_static()); - ASSERT_EQ(data.get({}).cast(), 2); - ASSERT_TRUE(data.set({}, 42)); - ASSERT_EQ(data.get({}).cast(), 42); - - data.prop([](auto prop) { - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_int); - ASSERT_EQ(prop.value(), 2); - }); - - ASSERT_FALSE(data.prop(properties::prop_bool)); - - auto prop = data.prop(properties::prop_int); - - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_int); - ASSERT_EQ(prop.value(), 2); -} - -TEST_F(Meta, MetaDataConstStatic) { - auto data = entt::resolve().data("k"); - - ASSERT_TRUE(data); - ASSERT_EQ(data.parent(), entt::resolve("data")); - ASSERT_EQ(data.type(), entt::resolve()); - ASSERT_STREQ(data.name(), "k"); - ASSERT_TRUE(data.is_const()); - ASSERT_TRUE(data.is_static()); - ASSERT_EQ(data.get({}).cast(), 3); - ASSERT_FALSE(data.set({}, 42)); - ASSERT_EQ(data.get({}).cast(), 3); - - data.prop([](auto prop) { - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_int); - ASSERT_EQ(prop.value(), 3); - }); - - ASSERT_FALSE(data.prop(properties::prop_bool)); - - auto prop = data.prop(properties::prop_int); - - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_int); - ASSERT_EQ(prop.value(), 3); -} - -TEST_F(Meta, MetaDataGetMetaAnyArg) { - auto data = entt::resolve().data("i"); - entt::meta_any any{data_type{}}; - any.cast().i = 99; - const auto value = data.get(any); - - ASSERT_TRUE(value); - ASSERT_TRUE(value.can_cast()); - ASSERT_EQ(value.cast(), 99); -} - -TEST_F(Meta, MetaDataGetInvalidArg) { - ASSERT_FALSE(entt::resolve().data("i").get(0)); -} - -TEST_F(Meta, MetaDataSetMetaAnyArg) { - auto data = entt::resolve().data("i"); - entt::meta_any any{data_type{}}; - entt::meta_any value{42}; - - ASSERT_EQ(any.cast().i, 0); - ASSERT_TRUE(data.set(any, value)); - ASSERT_EQ(any.cast().i, 42); -} - -TEST_F(Meta, MetaDataSetInvalidArg) { - ASSERT_FALSE(entt::resolve().data("i").set({}, 'c')); -} - -TEST_F(Meta, MetaDataSetCast) { - auto data = entt::resolve().data("empty"); - data_type instance{}; - - ASSERT_EQ(empty_type::counter, 0); - ASSERT_TRUE(data.set(instance, fat_type{})); - ASSERT_EQ(empty_type::counter, 1); -} - -TEST_F(Meta, MetaDataSetConvert) { - auto data = entt::resolve().data("i"); - data_type instance{}; - - ASSERT_EQ(instance.i, 0); - ASSERT_TRUE(data.set(instance, 3.)); - ASSERT_EQ(instance.i, 3); -} - -TEST_F(Meta, MetaDataSetterGetterAsFreeFunctions) { - auto data = entt::resolve().data("x"); - setter_getter_type instance{}; - - ASSERT_TRUE(data); - ASSERT_NE(data, entt::meta_data{}); - ASSERT_EQ(data.parent(), entt::resolve("setter_getter")); - ASSERT_EQ(data.type(), entt::resolve()); - ASSERT_STREQ(data.name(), "x"); - ASSERT_FALSE(data.is_const()); - ASSERT_FALSE(data.is_static()); - ASSERT_EQ(data.get(instance).cast(), 0); - ASSERT_TRUE(data.set(instance, 42)); - ASSERT_EQ(data.get(instance).cast(), 42); -} - -TEST_F(Meta, MetaDataSetterGetterAsMemberFunctions) { - auto data = entt::resolve().data("y"); - setter_getter_type instance{}; - - ASSERT_TRUE(data); - ASSERT_NE(data, entt::meta_data{}); - ASSERT_EQ(data.parent(), entt::resolve("setter_getter")); - ASSERT_EQ(data.type(), entt::resolve()); - ASSERT_STREQ(data.name(), "y"); - ASSERT_FALSE(data.is_const()); - ASSERT_FALSE(data.is_static()); - ASSERT_EQ(data.get(instance).cast(), 0); - ASSERT_TRUE(data.set(instance, 42)); - ASSERT_EQ(data.get(instance).cast(), 42); -} - -TEST_F(Meta, MetaDataSetterGetterWithRefAsMemberFunctions) { - auto data = entt::resolve().data("w"); - setter_getter_type instance{}; - - ASSERT_TRUE(data); - ASSERT_NE(data, entt::meta_data{}); - ASSERT_EQ(data.parent(), entt::resolve("setter_getter")); - ASSERT_EQ(data.type(), entt::resolve()); - ASSERT_STREQ(data.name(), "w"); - ASSERT_FALSE(data.is_const()); - ASSERT_FALSE(data.is_static()); - ASSERT_EQ(data.get(instance).cast(), 0); - ASSERT_TRUE(data.set(instance, 42)); - ASSERT_EQ(data.get(instance).cast(), 42); -} - -TEST_F(Meta, MetaDataSetterGetterMixed) { - auto data = entt::resolve().data("z"); - setter_getter_type instance{}; - - ASSERT_TRUE(data); - ASSERT_NE(data, entt::meta_data{}); - ASSERT_EQ(data.parent(), entt::resolve("setter_getter")); - ASSERT_EQ(data.type(), entt::resolve()); - ASSERT_STREQ(data.name(), "z"); - ASSERT_FALSE(data.is_const()); - ASSERT_FALSE(data.is_static()); - ASSERT_EQ(data.get(instance).cast(), 0); - ASSERT_TRUE(data.set(instance, 42)); - ASSERT_EQ(data.get(instance).cast(), 42); -} - -TEST_F(Meta, MetaDataArrayStatic) { - auto data = entt::resolve().data("global"); - - array_type::global[0] = 3; - array_type::global[1] = 5; - array_type::global[2] = 7; - - ASSERT_TRUE(data); - ASSERT_NE(data, entt::meta_data{}); - ASSERT_EQ(data.parent(), entt::resolve("array")); - ASSERT_EQ(data.type(), entt::resolve()); - ASSERT_STREQ(data.name(), "global"); - ASSERT_FALSE(data.is_const()); - ASSERT_TRUE(data.is_static()); - ASSERT_TRUE(data.type().is_array()); - ASSERT_EQ(data.type().extent(), 3); - ASSERT_EQ(data.get(nullptr, 0).cast(), 3); - ASSERT_EQ(data.get(nullptr, 1).cast(), 5); - ASSERT_EQ(data.get(nullptr, 2).cast(), 7); - ASSERT_FALSE(data.set(nullptr, 0, 'c')); - ASSERT_EQ(data.get(nullptr, 0).cast(), 3); - ASSERT_TRUE(data.set(nullptr, 0, data.get(nullptr, 0).cast()+2)); - ASSERT_TRUE(data.set(nullptr, 1, data.get(nullptr, 1).cast()+2)); - ASSERT_TRUE(data.set(nullptr, 2, data.get(nullptr, 2).cast()+2)); - ASSERT_EQ(data.get(nullptr, 0).cast(), 5); - ASSERT_EQ(data.get(nullptr, 1).cast(), 7); - ASSERT_EQ(data.get(nullptr, 2).cast(), 9); -} - -TEST_F(Meta, MetaDataArray) { - auto data = entt::resolve().data("local"); - array_type instance; - - instance.local[0] = 3; - instance.local[1] = 5; - instance.local[2] = 7; - - ASSERT_TRUE(data); - ASSERT_NE(data, entt::meta_data{}); - ASSERT_EQ(data.parent(), entt::resolve("array")); - ASSERT_EQ(data.type(), entt::resolve()); - ASSERT_STREQ(data.name(), "local"); - ASSERT_FALSE(data.is_const()); - ASSERT_FALSE(data.is_static()); - ASSERT_TRUE(data.type().is_array()); - ASSERT_EQ(data.type().extent(), 3); - ASSERT_EQ(data.get(instance, 0).cast(), 3); - ASSERT_EQ(data.get(instance, 1).cast(), 5); - ASSERT_EQ(data.get(instance, 2).cast(), 7); - ASSERT_FALSE(data.set(instance, 0, 'c')); - ASSERT_EQ(data.get(instance, 0).cast(), 3); - ASSERT_TRUE(data.set(instance, 0, data.get(instance, 0).cast()+2)); - ASSERT_TRUE(data.set(instance, 1, data.get(instance, 1).cast()+2)); - ASSERT_TRUE(data.set(instance, 2, data.get(instance, 2).cast()+2)); - ASSERT_EQ(data.get(instance, 0).cast(), 5); - ASSERT_EQ(data.get(instance, 1).cast(), 7); - ASSERT_EQ(data.get(instance, 2).cast(), 9); -} - -TEST_F(Meta, MetaFunc) { - auto func = entt::resolve().func("f2"); - func_type instance{}; - - ASSERT_TRUE(func); - ASSERT_NE(func, entt::meta_func{}); - ASSERT_EQ(func.parent(), entt::resolve("func")); - ASSERT_STREQ(func.name(), "f2"); - ASSERT_EQ(func.size(), entt::meta_func::size_type{2}); - ASSERT_FALSE(func.is_const()); - ASSERT_FALSE(func.is_static()); - ASSERT_EQ(func.ret(), entt::resolve()); - ASSERT_EQ(func.arg(entt::meta_func::size_type{0}), entt::resolve()); - ASSERT_EQ(func.arg(entt::meta_func::size_type{1}), entt::resolve()); - ASSERT_FALSE(func.arg(entt::meta_func::size_type{2})); - - auto any = func.invoke(instance, 3, 2); - auto empty = func.invoke(instance); - - ASSERT_FALSE(empty); - ASSERT_TRUE(any); - ASSERT_EQ(any.type(), entt::resolve()); - ASSERT_EQ(any.cast(), 4); - ASSERT_EQ(func_type::value, 3); - - func.prop([](auto prop) { - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_bool); - ASSERT_FALSE(prop.value().template cast()); - }); - - ASSERT_FALSE(func.prop(properties::prop_int)); - - auto prop = func.prop(properties::prop_bool); - - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_bool); - ASSERT_FALSE(prop.value().cast()); -} - -TEST_F(Meta, MetaFuncConst) { - auto func = entt::resolve().func("f1"); - func_type instance{}; - - ASSERT_TRUE(func); - ASSERT_EQ(func.parent(), entt::resolve("func")); - ASSERT_STREQ(func.name(), "f1"); - ASSERT_EQ(func.size(), entt::meta_func::size_type{1}); - ASSERT_TRUE(func.is_const()); - ASSERT_FALSE(func.is_static()); - ASSERT_EQ(func.ret(), entt::resolve()); - ASSERT_EQ(func.arg(entt::meta_func::size_type{0}), entt::resolve()); - ASSERT_FALSE(func.arg(entt::meta_func::size_type{1})); - - auto any = func.invoke(instance, 4); - auto empty = func.invoke(instance, 'c'); - - ASSERT_FALSE(empty); - ASSERT_TRUE(any); - ASSERT_EQ(any.type(), entt::resolve()); - ASSERT_EQ(any.cast(), 16); - - func.prop([](auto prop) { - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_bool); - ASSERT_FALSE(prop.value().template cast()); - }); - - ASSERT_FALSE(func.prop(properties::prop_int)); - - auto prop = func.prop(properties::prop_bool); - - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_bool); - ASSERT_FALSE(prop.value().cast()); -} - -TEST_F(Meta, MetaFuncRetVoid) { - auto func = entt::resolve().func("g"); - func_type instance{}; - - ASSERT_TRUE(func); - ASSERT_EQ(func.parent(), entt::resolve("func")); - ASSERT_STREQ(func.name(), "g"); - ASSERT_EQ(func.size(), entt::meta_func::size_type{1}); - ASSERT_FALSE(func.is_const()); - ASSERT_FALSE(func.is_static()); - ASSERT_EQ(func.ret(), entt::resolve()); - ASSERT_EQ(func.arg(entt::meta_func::size_type{0}), entt::resolve()); - ASSERT_FALSE(func.arg(entt::meta_func::size_type{1})); - - auto any = func.invoke(instance, 5); - - ASSERT_FALSE(any); - ASSERT_EQ(func_type::value, 25); - - func.prop([](auto prop) { - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_bool); - ASSERT_FALSE(prop.value().template cast()); - }); - - ASSERT_FALSE(func.prop(properties::prop_int)); - - auto prop = func.prop(properties::prop_bool); - - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_bool); - ASSERT_FALSE(prop.value().cast()); -} - -TEST_F(Meta, MetaFuncStatic) { - auto func = entt::resolve().func("h"); - - ASSERT_TRUE(func); - ASSERT_EQ(func.parent(), entt::resolve("func")); - ASSERT_STREQ(func.name(), "h"); - ASSERT_EQ(func.size(), entt::meta_func::size_type{1}); - ASSERT_FALSE(func.is_const()); - ASSERT_TRUE(func.is_static()); - ASSERT_EQ(func.ret(), entt::resolve()); - ASSERT_EQ(func.arg(entt::meta_func::size_type{0}), entt::resolve()); - ASSERT_FALSE(func.arg(entt::meta_func::size_type{1})); - - auto any = func.invoke({}, 42); - auto empty = func.invoke({}, 'c'); - - ASSERT_FALSE(empty); - ASSERT_TRUE(any); - ASSERT_EQ(any.type(), entt::resolve()); - ASSERT_EQ(any.cast(), 42); - - func.prop([](auto prop) { - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_bool); - ASSERT_FALSE(prop.value().template cast()); - }); - - ASSERT_FALSE(func.prop(properties::prop_int)); - - auto prop = func.prop(properties::prop_bool); - - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_bool); - ASSERT_FALSE(prop.value().cast()); -} - -TEST_F(Meta, MetaFuncStaticRetVoid) { - auto func = entt::resolve().func("k"); - - ASSERT_TRUE(func); - ASSERT_EQ(func.parent(), entt::resolve("func")); - ASSERT_STREQ(func.name(), "k"); - ASSERT_EQ(func.size(), entt::meta_func::size_type{1}); - ASSERT_FALSE(func.is_const()); - ASSERT_TRUE(func.is_static()); - ASSERT_EQ(func.ret(), entt::resolve()); - ASSERT_EQ(func.arg(entt::meta_func::size_type{0}), entt::resolve()); - ASSERT_FALSE(func.arg(entt::meta_func::size_type{1})); - - auto any = func.invoke({}, 42); - - ASSERT_FALSE(any); - ASSERT_EQ(func_type::value, 42); - - func.prop([](auto *prop) { - ASSERT_TRUE(prop); - ASSERT_EQ(prop->key(), properties::prop_bool); - ASSERT_FALSE(prop->value().template cast()); - }); - - ASSERT_FALSE(func.prop(properties::prop_int)); - - auto prop = func.prop(properties::prop_bool); - - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_bool); - ASSERT_FALSE(prop.value().cast()); -} - -TEST_F(Meta, MetaFuncMetaAnyArgs) { - auto func = entt::resolve().func("f1"); - auto any = func.invoke(func_type{}, entt::meta_any{3}); - - ASSERT_TRUE(any); - ASSERT_EQ(any.type(), entt::resolve()); - ASSERT_EQ(any.cast(), 9); -} - -TEST_F(Meta, MetaFuncInvalidArgs) { - auto func = entt::resolve().func("f1"); - ASSERT_FALSE(func.invoke(empty_type{}, entt::meta_any{'c'})); -} - -TEST_F(Meta, MetaFuncCastAndConvert) { - auto func = entt::resolve().func("f3"); - auto any = func.invoke(func_type{}, derived_type{}, 0, 3.); - - ASSERT_TRUE(any); - ASSERT_EQ(any.type(), entt::resolve()); - ASSERT_EQ(any.cast(), 9); -} - -TEST_F(Meta, MetaType) { - auto type = entt::resolve(); - - ASSERT_TRUE(type); - ASSERT_NE(type, entt::meta_type{}); - ASSERT_STREQ(type.name(), "derived"); - - type.prop([](auto prop) { - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_int); - ASSERT_EQ(prop.value(), 99); - }); - - ASSERT_FALSE(type.prop(properties::prop_bool)); - - auto prop = type.prop(properties::prop_int); - - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_int); - ASSERT_EQ(prop.value(), 99); -} - -TEST_F(Meta, MetaTypeTraits) { - ASSERT_TRUE(entt::resolve().is_void()); - ASSERT_TRUE(entt::resolve().is_integral()); - ASSERT_TRUE(entt::resolve().is_floating_point()); - ASSERT_TRUE(entt::resolve().is_enum()); - ASSERT_TRUE(entt::resolve().is_union()); - ASSERT_TRUE(entt::resolve().is_class()); - ASSERT_TRUE(entt::resolve().is_pointer()); - ASSERT_TRUE(entt::resolve().is_function()); - ASSERT_TRUE(entt::resolve().is_member_object_pointer()); - ASSERT_TRUE(entt::resolve().is_member_function_pointer()); -} - -TEST_F(Meta, MetaTypeRemovePointer) { - ASSERT_EQ(entt::resolve().remove_pointer(), entt::resolve()); - ASSERT_EQ(entt::resolve().remove_pointer(), entt::resolve()); - ASSERT_EQ(entt::resolve().remove_pointer(), entt::resolve()); -} - -TEST_F(Meta, MetaTypeBase) { - auto type = entt::resolve(); - bool iterate = false; - - type.base([&iterate](auto base) { - ASSERT_EQ(base.type(), entt::resolve()); - iterate = true; - }); - - ASSERT_TRUE(iterate); - ASSERT_EQ(type.base("base").type(), entt::resolve()); -} - -TEST_F(Meta, MetaTypeConv) { - auto type = entt::resolve(); - bool iterate = false; - - type.conv([&iterate](auto conv) { - ASSERT_EQ(conv.type(), entt::resolve()); - iterate = true; - }); - - ASSERT_TRUE(iterate); - - auto conv = type.conv(); - - ASSERT_EQ(conv.type(), entt::resolve()); - ASSERT_FALSE(type.conv()); -} - -TEST_F(Meta, MetaTypeCtor) { - auto type = entt::resolve(); - int counter{}; - - type.ctor([&counter](auto) { - ++counter; - }); - - ASSERT_EQ(counter, 2); - ASSERT_TRUE((type.ctor())); - ASSERT_TRUE((type.ctor())); -} - -TEST_F(Meta, MetaTypeDtor) { - ASSERT_TRUE(entt::resolve().dtor()); - ASSERT_FALSE(entt::resolve().dtor()); -} - -TEST_F(Meta, MetaTypeData) { - auto type = entt::resolve(); - int counter{}; - - type.data([&counter](auto) { - ++counter; - }); - - ASSERT_EQ(counter, 5); - ASSERT_TRUE(type.data("i")); -} - -TEST_F(Meta, MetaTypeFunc) { - auto type = entt::resolve(); - int counter{}; - - type.func([&counter](auto) { - ++counter; - }); - - ASSERT_EQ(counter, 6); - ASSERT_TRUE(type.func("f1")); -} - -TEST_F(Meta, MetaTypeConstruct) { - auto type = entt::resolve(); - auto any = type.construct(base_type{}, 42, 'c'); - - ASSERT_TRUE(any); - ASSERT_TRUE(any.can_cast()); - ASSERT_EQ(any.cast().i, 42); - ASSERT_EQ(any.cast().c, 'c'); -} - -TEST_F(Meta, MetaTypeConstructMetaAnyArgs) { - auto type = entt::resolve(); - auto any = type.construct(entt::meta_any{base_type{}}, entt::meta_any{42}, entt::meta_any{'c'}); - - ASSERT_TRUE(any); - ASSERT_TRUE(any.can_cast()); - ASSERT_EQ(any.cast().i, 42); - ASSERT_EQ(any.cast().c, 'c'); -} - -TEST_F(Meta, MetaTypeConstructInvalidArgs) { - auto type = entt::resolve(); - auto any = type.construct(entt::meta_any{base_type{}}, entt::meta_any{'c'}, entt::meta_any{42}); - ASSERT_FALSE(any); -} - -TEST_F(Meta, MetaTypeConstructCastAndConvert) { - auto type = entt::resolve(); - auto any = type.construct(entt::meta_any{derived_type{}}, entt::meta_any{42.}, entt::meta_any{'c'}); - - ASSERT_TRUE(any); - ASSERT_TRUE(any.can_cast()); - ASSERT_EQ(any.cast().i, 42); - ASSERT_EQ(any.cast().c, 'c'); -} - -TEST_F(Meta, MetaTypeDestroyDtor) { - auto type = entt::resolve(); - - ASSERT_EQ(empty_type::counter, 0); - ASSERT_TRUE(type.destroy(empty_type{})); - ASSERT_EQ(empty_type::counter, 1); -} - -TEST_F(Meta, MetaTypeDestroyDtorInvalidArg) { - auto type = entt::resolve(); - - ASSERT_EQ(empty_type::counter, 0); - ASSERT_FALSE(type.destroy('c')); - ASSERT_EQ(empty_type::counter, 0); -} - -TEST_F(Meta, MetaTypeDestroyDtorCastAndConvert) { - auto type = entt::resolve(); - - ASSERT_EQ(empty_type::counter, 0); - ASSERT_FALSE(type.destroy(fat_type{})); - ASSERT_EQ(empty_type::counter, 0); - ASSERT_FALSE(entt::resolve().destroy(42.)); -} - -TEST_F(Meta, MetaTypeDestroyNoDtor) { - ASSERT_TRUE(entt::resolve().destroy('c')); -} - -TEST_F(Meta, MetaTypeDestroyNoDtorInvalidArg) { - ASSERT_FALSE(entt::resolve().destroy(42)); -} - -TEST_F(Meta, MetaTypeDestroyNoDtorVoid) { - ASSERT_FALSE(entt::resolve().destroy({})); -} - -TEST_F(Meta, MetaTypeDestroyNoDtorCastAndConvert) { - ASSERT_FALSE(entt::resolve().destroy(42.)); -} - -TEST_F(Meta, MetaDataFromBase) { - auto type = entt::resolve(); - concrete_type instance; - - ASSERT_TRUE(type.data("i")); - ASSERT_TRUE(type.data("j")); - - ASSERT_EQ(instance.i, 0); - ASSERT_EQ(instance.j, char{}); - ASSERT_TRUE(type.data("i").set(instance, 3)); - ASSERT_TRUE(type.data("j").set(instance, 'c')); - ASSERT_EQ(instance.i, 3); - ASSERT_EQ(instance.j, 'c'); -} - -TEST_F(Meta, MetaFuncFromBase) { - auto type = entt::resolve(); - auto base = entt::resolve(); - concrete_type instance; - - ASSERT_TRUE(type.func("f")); - ASSERT_TRUE(type.func("g")); - ASSERT_TRUE(type.func("h")); - - ASSERT_EQ(type.func("f").parent(), entt::resolve()); - ASSERT_EQ(type.func("g").parent(), entt::resolve()); - ASSERT_EQ(type.func("h").parent(), entt::resolve()); - - ASSERT_EQ(instance.i, 0); - ASSERT_EQ(instance.j, char{}); - - type.func("f").invoke(instance, 3); - type.func("h").invoke(instance, 'c'); - - ASSERT_EQ(instance.i, 9); - ASSERT_EQ(instance.j, 'c'); - - base.func("g").invoke(instance, 3); - - ASSERT_EQ(instance.i, -3); -} - -TEST_F(Meta, MetaPropFromBase) { - auto type = entt::resolve(); - auto prop_bool = type.prop(properties::prop_bool); - auto prop_int = type.prop(properties::prop_int); - - ASSERT_TRUE(prop_bool); - ASSERT_TRUE(prop_int); - - ASSERT_FALSE(prop_bool.value().cast()); - ASSERT_EQ(prop_int.value().cast(), 42); -} - -TEST_F(Meta, AbstractClass) { - auto type = entt::resolve(); - concrete_type instance; - - ASSERT_EQ(instance.i, 0); - - type.func("f").invoke(instance, 3); - - ASSERT_EQ(instance.i, 3); - - type.func("g").invoke(instance, 3); - - ASSERT_EQ(instance.i, -3); -} - -TEST_F(Meta, EnumAndNamedConstants) { - auto type = entt::resolve(); - - ASSERT_TRUE(type.data("prop_bool")); - ASSERT_TRUE(type.data("prop_int")); - - ASSERT_EQ(type.data("prop_bool").type(), type); - ASSERT_EQ(type.data("prop_int").type(), type); - - ASSERT_FALSE(type.data("prop_bool").set({}, properties::prop_int)); - ASSERT_FALSE(type.data("prop_int").set({}, properties::prop_bool)); - - ASSERT_EQ(type.data("prop_bool").get({}).cast(), properties::prop_bool); - ASSERT_EQ(type.data("prop_int").get({}).cast(), properties::prop_int); -} - -TEST_F(Meta, ArithmeticTypeAndNamedConstants) { - auto type = entt::resolve(); - - ASSERT_TRUE(type.data("min")); - ASSERT_TRUE(type.data("max")); - - ASSERT_EQ(type.data("min").type(), type); - ASSERT_EQ(type.data("max").type(), type); - - ASSERT_FALSE(type.data("min").set({}, 100u)); - ASSERT_FALSE(type.data("max").set({}, 0u)); - - ASSERT_EQ(type.data("min").get({}).cast(), 0u); - ASSERT_EQ(type.data("max").get({}).cast(), 100u); -} - -TEST_F(Meta, Unregister) { - entt::unregister(); - entt::unregister(); - entt::unregister(); - entt::unregister(); - entt::unregister(); - entt::unregister(); - entt::unregister(); - entt::unregister(); - entt::unregister(); - entt::unregister(); - entt::unregister(); - entt::unregister(); - entt::unregister(); - entt::unregister(); - - ASSERT_FALSE(entt::resolve("char")); - ASSERT_FALSE(entt::resolve("base")); - ASSERT_FALSE(entt::resolve("derived")); - ASSERT_FALSE(entt::resolve("empty")); - ASSERT_FALSE(entt::resolve("fat")); - ASSERT_FALSE(entt::resolve("data")); - ASSERT_FALSE(entt::resolve("func")); - ASSERT_FALSE(entt::resolve("setter_getter")); - ASSERT_FALSE(entt::resolve("an_abstract_type")); - ASSERT_FALSE(entt::resolve("another_abstract_type")); - ASSERT_FALSE(entt::resolve("concrete")); - - Meta::SetUpAfterUnregistration(); - entt::meta_any any{42.}; - - ASSERT_TRUE(any); - ASSERT_FALSE(any.can_convert()); - ASSERT_TRUE(any.can_convert()); - - ASSERT_FALSE(entt::resolve("derived")); - ASSERT_TRUE(entt::resolve("my_type")); - - entt::resolve().prop([](auto prop) { - ASSERT_TRUE(prop); - ASSERT_EQ(prop.key(), properties::prop_bool); - ASSERT_FALSE(prop.value().template cast()); - }); - - ASSERT_FALSE((entt::resolve().ctor())); - ASSERT_TRUE((entt::resolve().ctor<>())); - - ASSERT_TRUE(entt::resolve("your_type").data("a_data_member")); - ASSERT_FALSE(entt::resolve("your_type").data("another_data_member")); - - ASSERT_TRUE(entt::resolve("your_type").func("a_member_function")); - ASSERT_FALSE(entt::resolve("your_type").func("another_member_function")); -} diff --git a/modules/entt/test/entt/meta/meta_any.cpp b/modules/entt/test/entt/meta/meta_any.cpp new file mode 100644 index 0000000..1440b60 --- /dev/null +++ b/modules/entt/test/entt/meta/meta_any.cpp @@ -0,0 +1,1385 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct clazz_t { + clazz_t() + : value{0} {} + + void member(int i) { + value = i; + } + + static void func() { + c = 'd'; + } + + operator int() const { + return value; + } + + static inline char c = 'c'; + int value; +}; + +struct empty_t { + virtual ~empty_t() { + ++destructor_counter; + } + + static void destroy(empty_t &) { + ++destroy_counter; + } + + inline static int destroy_counter = 0; + inline static int destructor_counter = 0; +}; + +struct fat_t: empty_t { + fat_t() + : value{.0, .0, .0, .0} {} + + fat_t(double v1, double v2, double v3, double v4) + : value{v1, v2, v3, v4} {} + + bool operator==(const fat_t &other) const { + return std::equal(std::begin(value), std::end(value), std::begin(other.value), std::end(other.value)); + } + + double value[4]; +}; + +enum class enum_class : unsigned short int { + foo = 0u, + bar = 42u +}; + +struct not_comparable_t { + bool operator==(const not_comparable_t &) const = delete; +}; + +struct unmanageable_t { + unmanageable_t() = default; + unmanageable_t(const unmanageable_t &) = delete; + unmanageable_t(unmanageable_t &&) = delete; + unmanageable_t &operator=(const unmanageable_t &) = delete; + unmanageable_t &operator=(unmanageable_t &&) = delete; +}; + +struct MetaAny: ::testing::Test { + void SetUp() override { + using namespace entt::literals; + + entt::meta() + .type("double"_hs); + + entt::meta() + .type("empty"_hs) + .dtor(); + + entt::meta() + .type("fat"_hs) + .base() + .dtor(); + + entt::meta() + .type("clazz"_hs) + .data<&clazz_t::value>("value"_hs) + .func<&clazz_t::member>("member"_hs) + .func("func"_hs) + .conv(); + + empty_t::destroy_counter = 0; + empty_t::destructor_counter = 0; + } + + void TearDown() override { + entt::meta_reset(); + } +}; + +using MetaAnyDeathTest = MetaAny; + +TEST_F(MetaAny, SBO) { + entt::meta_any any{'c'}; + + ASSERT_TRUE(any); + ASSERT_TRUE(any.owner()); + ASSERT_FALSE(any.try_cast()); + ASSERT_EQ(any.cast(), 'c'); + ASSERT_NE(any.data(), nullptr); + ASSERT_EQ(any, entt::meta_any{'c'}); + ASSERT_NE(entt::meta_any{'h'}, any); +} + +TEST_F(MetaAny, NoSBO) { + fat_t instance{.1, .2, .3, .4}; + entt::meta_any any{instance}; + + ASSERT_TRUE(any); + ASSERT_TRUE(any.owner()); + ASSERT_FALSE(any.try_cast()); + ASSERT_EQ(any.cast(), instance); + ASSERT_NE(any.data(), nullptr); + ASSERT_EQ(any, entt::meta_any{instance}); + ASSERT_NE(fat_t{}, any); +} + +TEST_F(MetaAny, Empty) { + entt::meta_any any{}; + + ASSERT_FALSE(any); + ASSERT_FALSE(any.type()); + ASSERT_FALSE(any.try_cast()); + ASSERT_EQ(any.data(), nullptr); + ASSERT_EQ(any, entt::meta_any{}); + ASSERT_NE(entt::meta_any{'c'}, any); + + ASSERT_FALSE(any.as_ref()); + ASSERT_FALSE(any.as_sequence_container()); + ASSERT_FALSE(any.as_associative_container()); + + ASSERT_FALSE(std::as_const(any).as_ref()); + ASSERT_FALSE(std::as_const(any).as_sequence_container()); + ASSERT_FALSE(std::as_const(any).as_associative_container()); +} + +TEST_F(MetaAny, SBOInPlaceTypeConstruction) { + entt::meta_any any{std::in_place_type, 42}; + + ASSERT_TRUE(any); + ASSERT_FALSE(any.try_cast()); + ASSERT_EQ(any.cast(), 42); + ASSERT_NE(any.data(), nullptr); + ASSERT_EQ(any, (entt::meta_any{std::in_place_type, 42})); + ASSERT_EQ(any, entt::meta_any{42}); + ASSERT_NE(entt::meta_any{3}, any); +} + +TEST_F(MetaAny, SBOAsRefConstruction) { + int value = 3; + int compare = 42; + auto any = entt::forward_as_meta(value); + + ASSERT_TRUE(any); + ASSERT_FALSE(any.owner()); + ASSERT_EQ(any.type(), entt::resolve()); + + ASSERT_FALSE(any.try_cast()); + ASSERT_EQ(any.cast(), 3); + ASSERT_EQ(any.cast(), 3); + ASSERT_EQ(any.cast(), 3); + ASSERT_EQ(any.data(), &value); + ASSERT_EQ(std::as_const(any).data(), &value); + + ASSERT_EQ(any, entt::forward_as_meta(value)); + ASSERT_NE(any, entt::forward_as_meta(compare)); + + ASSERT_NE(any, entt::meta_any{42}); + ASSERT_EQ(entt::meta_any{3}, any); + + any = entt::make_meta(value); + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(std::as_const(any).data(), &value); + + auto other = any.as_ref(); + + ASSERT_TRUE(other); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), 3); + ASSERT_EQ(other.data(), any.data()); +} + +TEST_F(MetaAny, SBOAsConstRefConstruction) { + const int value = 3; + int compare = 42; + auto any = entt::forward_as_meta(value); + + ASSERT_TRUE(any); + ASSERT_FALSE(any.owner()); + ASSERT_EQ(any.type(), entt::resolve()); + + ASSERT_FALSE(any.try_cast()); + ASSERT_EQ(any.cast(), 3); + ASSERT_EQ(any.cast(), 3); + ASSERT_EQ(any.data(), nullptr); + ASSERT_EQ(std::as_const(any).data(), &value); + + ASSERT_EQ(any, entt::forward_as_meta(value)); + ASSERT_NE(any, entt::forward_as_meta(compare)); + + ASSERT_NE(any, entt::meta_any{42}); + ASSERT_EQ(entt::meta_any{3}, any); + + any = entt::make_meta(value); + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(std::as_const(any).data(), &value); + + auto other = any.as_ref(); + + ASSERT_TRUE(other); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), 3); + ASSERT_EQ(other.data(), any.data()); +} + +TEST_F(MetaAnyDeathTest, SBOAsConstRefConstruction) { + const int value = 3; + auto any = entt::forward_as_meta(value); + + ASSERT_TRUE(any); + ASSERT_DEATH(any.cast() = 3, ""); +} + +TEST_F(MetaAny, SBOCopyConstruction) { + entt::meta_any any{42}; + entt::meta_any other{any}; + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + ASSERT_FALSE(other.try_cast()); + ASSERT_EQ(other.cast(), 42); + ASSERT_EQ(other, entt::meta_any{42}); + ASSERT_NE(other, entt::meta_any{0}); +} + +TEST_F(MetaAny, SBOCopyAssignment) { + entt::meta_any any{42}; + entt::meta_any other{3}; + + other = any; + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + ASSERT_FALSE(other.try_cast()); + ASSERT_EQ(other.cast(), 42); + ASSERT_EQ(other, entt::meta_any{42}); + ASSERT_NE(other, entt::meta_any{0}); +} + +TEST_F(MetaAny, SBOMoveConstruction) { + entt::meta_any any{42}; + entt::meta_any other{std::move(any)}; + + ASSERT_FALSE(any); + ASSERT_TRUE(other); + ASSERT_FALSE(other.try_cast()); + ASSERT_EQ(other.cast(), 42); + ASSERT_EQ(other, entt::meta_any{42}); + ASSERT_NE(other, entt::meta_any{0}); +} + +TEST_F(MetaAny, SBOMoveAssignment) { + entt::meta_any any{42}; + entt::meta_any other{3}; + + other = std::move(any); + + ASSERT_FALSE(any); + ASSERT_TRUE(other); + ASSERT_FALSE(other.try_cast()); + ASSERT_EQ(other.cast(), 42); + ASSERT_EQ(other, entt::meta_any{42}); + ASSERT_NE(other, entt::meta_any{0}); +} + +TEST_F(MetaAny, SBODirectAssignment) { + entt::meta_any any{}; + any = 42; + + ASSERT_FALSE(any.try_cast()); + ASSERT_EQ(any.cast(), 42); + ASSERT_EQ(any, entt::meta_any{42}); + ASSERT_NE(entt::meta_any{0}, any); +} + +TEST_F(MetaAny, SBOAssignValue) { + entt::meta_any any{42}; + entt::meta_any other{3}; + entt::meta_any invalid{empty_t{}}; + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast(), 42); + + ASSERT_TRUE(any.assign(other)); + ASSERT_FALSE(any.assign(invalid)); + ASSERT_EQ(any.cast(), 3); +} + +TEST_F(MetaAny, SBOConvertAssignValue) { + entt::meta_any any{42}; + entt::meta_any other{3.5}; + entt::meta_any invalid{empty_t{}}; + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast(), 42); + + ASSERT_TRUE(any.assign(other)); + ASSERT_FALSE(any.assign(invalid)); + ASSERT_EQ(any.cast(), 3); +} + +TEST_F(MetaAny, SBOAsRefAssignValue) { + int value = 42; + entt::meta_any any{entt::forward_as_meta(value)}; + entt::meta_any other{3}; + entt::meta_any invalid{empty_t{}}; + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast(), 42); + + ASSERT_TRUE(any.assign(other)); + ASSERT_FALSE(any.assign(invalid)); + ASSERT_EQ(any.cast(), 3); + ASSERT_EQ(value, 3); +} + +TEST_F(MetaAny, SBOAsConstRefAssignValue) { + const int value = 42; + entt::meta_any any{entt::forward_as_meta(value)}; + entt::meta_any other{3}; + entt::meta_any invalid{empty_t{}}; + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast(), 42); + + ASSERT_FALSE(any.assign(other)); + ASSERT_FALSE(any.assign(invalid)); + ASSERT_EQ(any.cast(), 42); + ASSERT_EQ(value, 42); +} + +TEST_F(MetaAny, SBOTransferValue) { + entt::meta_any any{42}; + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast(), 42); + + ASSERT_TRUE(any.assign(3)); + ASSERT_FALSE(any.assign(empty_t{})); + ASSERT_EQ(any.cast(), 3); +} + +TEST_F(MetaAny, SBOTransferConstValue) { + const int value = 3; + entt::meta_any any{42}; + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast(), 42); + + ASSERT_TRUE(any.assign(entt::forward_as_meta(value))); + ASSERT_EQ(any.cast(), 3); +} + +TEST_F(MetaAny, SBOConvertTransferValue) { + entt::meta_any any{42}; + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast(), 42); + + ASSERT_TRUE(any.assign(3.5)); + ASSERT_FALSE(any.assign(empty_t{})); + ASSERT_EQ(any.cast(), 3); +} + +TEST_F(MetaAny, SBOAsRefTransferValue) { + int value = 42; + entt::meta_any any{entt::forward_as_meta(value)}; + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast(), 42); + + ASSERT_TRUE(any.assign(3)); + ASSERT_FALSE(any.assign(empty_t{})); + ASSERT_EQ(any.cast(), 3); + ASSERT_EQ(value, 3); +} + +TEST_F(MetaAny, SBOAsConstRefTransferValue) { + const int value = 42; + entt::meta_any any{entt::forward_as_meta(value)}; + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast(), 42); + + ASSERT_FALSE(any.assign(3)); + ASSERT_FALSE(any.assign(empty_t{})); + ASSERT_EQ(any.cast(), 42); + ASSERT_EQ(value, 42); +} + +TEST_F(MetaAny, NoSBOInPlaceTypeConstruction) { + fat_t instance{.1, .2, .3, .4}; + entt::meta_any any{std::in_place_type, instance}; + + ASSERT_TRUE(any); + ASSERT_FALSE(any.try_cast()); + ASSERT_EQ(any.cast(), instance); + ASSERT_NE(any.data(), nullptr); + ASSERT_EQ(any, (entt::meta_any{std::in_place_type, instance})); + ASSERT_EQ(any, entt::meta_any{instance}); + ASSERT_NE(entt::meta_any{fat_t{}}, any); +} + +TEST_F(MetaAny, NoSBOAsRefConstruction) { + fat_t instance{.1, .2, .3, .4}; + auto any = entt::forward_as_meta(instance); + + ASSERT_TRUE(any); + ASSERT_FALSE(any.owner()); + ASSERT_EQ(any.type(), entt::resolve()); + + ASSERT_FALSE(any.try_cast()); + ASSERT_EQ(any.cast(), instance); + ASSERT_EQ(any.cast(), instance); + ASSERT_EQ(any.cast(), instance); + ASSERT_EQ(any.data(), &instance); + ASSERT_EQ(std::as_const(any).data(), &instance); + + ASSERT_EQ(any, entt::forward_as_meta(instance)); + + ASSERT_EQ(any, entt::meta_any{instance}); + ASSERT_NE(entt::meta_any{fat_t{}}, any); + + any = entt::make_meta(instance); + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(std::as_const(any).data(), &instance); + + auto other = any.as_ref(); + + ASSERT_TRUE(other); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any, entt::meta_any{instance}); + ASSERT_EQ(other.data(), any.data()); +} + +TEST_F(MetaAny, NoSBOAsConstRefConstruction) { + const fat_t instance{.1, .2, .3, .4}; + auto any = entt::forward_as_meta(instance); + + ASSERT_TRUE(any); + ASSERT_FALSE(any.owner()); + ASSERT_EQ(any.type(), entt::resolve()); + + ASSERT_FALSE(any.try_cast()); + ASSERT_EQ(any.cast(), instance); + ASSERT_EQ(any.cast(), instance); + ASSERT_EQ(any.data(), nullptr); + ASSERT_EQ(std::as_const(any).data(), &instance); + + ASSERT_EQ(any, entt::forward_as_meta(instance)); + + ASSERT_EQ(any, entt::meta_any{instance}); + ASSERT_NE(entt::meta_any{fat_t{}}, any); + + any = entt::make_meta(instance); + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(std::as_const(any).data(), &instance); + + auto other = any.as_ref(); + + ASSERT_TRUE(other); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any, entt::meta_any{instance}); + ASSERT_EQ(other.data(), any.data()); +} + +TEST_F(MetaAnyDeathTest, NoSBOAsConstRefConstruction) { + const fat_t instance{.1, .2, .3, .4}; + auto any = entt::forward_as_meta(instance); + + ASSERT_TRUE(any); + ASSERT_DEATH(any.cast() = {}, ""); +} + +TEST_F(MetaAny, NoSBOCopyConstruction) { + fat_t instance{.1, .2, .3, .4}; + entt::meta_any any{instance}; + entt::meta_any other{any}; + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + ASSERT_FALSE(other.try_cast()); + ASSERT_EQ(other.cast(), instance); + ASSERT_EQ(other, entt::meta_any{instance}); + ASSERT_NE(other, fat_t{}); +} + +TEST_F(MetaAny, NoSBOCopyAssignment) { + fat_t instance{.1, .2, .3, .4}; + entt::meta_any any{instance}; + entt::meta_any other{3}; + + other = any; + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + ASSERT_FALSE(other.try_cast()); + ASSERT_EQ(other.cast(), instance); + ASSERT_EQ(other, entt::meta_any{instance}); + ASSERT_NE(other, fat_t{}); +} + +TEST_F(MetaAny, NoSBOMoveConstruction) { + fat_t instance{.1, .2, .3, .4}; + entt::meta_any any{instance}; + entt::meta_any other{std::move(any)}; + + ASSERT_FALSE(any); + ASSERT_TRUE(other); + ASSERT_FALSE(other.try_cast()); + ASSERT_EQ(other.cast(), instance); + ASSERT_EQ(other, entt::meta_any{instance}); + ASSERT_NE(other, fat_t{}); +} + +TEST_F(MetaAny, NoSBOMoveAssignment) { + fat_t instance{.1, .2, .3, .4}; + entt::meta_any any{instance}; + entt::meta_any other{3}; + + other = std::move(any); + + ASSERT_FALSE(any); + ASSERT_TRUE(other); + ASSERT_FALSE(other.try_cast()); + ASSERT_EQ(other.cast(), instance); + ASSERT_EQ(other, entt::meta_any{instance}); + ASSERT_NE(other, fat_t{}); +} + +TEST_F(MetaAny, NoSBODirectAssignment) { + fat_t instance{.1, .2, .3, .4}; + entt::meta_any any{}; + any = instance; + + ASSERT_FALSE(any.try_cast()); + ASSERT_EQ(any.cast(), instance); + ASSERT_EQ(any, (entt::meta_any{fat_t{.1, .2, .3, .4}})); + ASSERT_NE(fat_t{}, any); +} + +TEST_F(MetaAny, NoSBOAssignValue) { + entt::meta_any any{fat_t{.1, .2, .3, .4}}; + entt::meta_any other{fat_t{.0, .1, .2, .3}}; + entt::meta_any invalid{'c'}; + + const void *addr = std::as_const(any).data(); + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast(), (fat_t{.1, .2, .3, .4})); + + ASSERT_TRUE(any.assign(other)); + ASSERT_FALSE(any.assign(invalid)); + ASSERT_EQ(any.cast(), (fat_t{.0, .1, .2, .3})); + ASSERT_EQ(addr, std::as_const(any).data()); +} + +TEST_F(MetaAny, NoSBOConvertAssignValue) { + entt::meta_any any{empty_t{}}; + entt::meta_any other{fat_t{.0, .1, .2, .3}}; + entt::meta_any invalid{'c'}; + + const void *addr = std::as_const(any).data(); + + ASSERT_TRUE(any); + ASSERT_TRUE(any.assign(other)); + ASSERT_FALSE(any.assign(invalid)); + ASSERT_EQ(addr, std::as_const(any).data()); +} + +TEST_F(MetaAny, NoSBOAsRefAssignValue) { + fat_t instance{.1, .2, .3, .4}; + entt::meta_any any{entt::forward_as_meta(instance)}; + entt::meta_any other{fat_t{.0, .1, .2, .3}}; + entt::meta_any invalid{'c'}; + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast(), (fat_t{.1, .2, .3, .4})); + + ASSERT_TRUE(any.assign(other)); + ASSERT_FALSE(any.assign(invalid)); + ASSERT_EQ(any.cast(), (fat_t{.0, .1, .2, .3})); + ASSERT_EQ(instance, (fat_t{.0, .1, .2, .3})); +} + +TEST_F(MetaAny, NoSBOAsConstRefAssignValue) { + const fat_t instance{.1, .2, .3, .4}; + entt::meta_any any{entt::forward_as_meta(instance)}; + entt::meta_any other{fat_t{.0, .1, .2, .3}}; + entt::meta_any invalid{'c'}; + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast(), (fat_t{.1, .2, .3, .4})); + + ASSERT_FALSE(any.assign(other)); + ASSERT_FALSE(any.assign(invalid)); + ASSERT_EQ(any.cast(), (fat_t{.1, .2, .3, .4})); + ASSERT_EQ(instance, (fat_t{.1, .2, .3, .4})); +} + +TEST_F(MetaAny, NoSBOTransferValue) { + entt::meta_any any{fat_t{.1, .2, .3, .4}}; + + const void *addr = std::as_const(any).data(); + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast(), (fat_t{.1, .2, .3, .4})); + + ASSERT_TRUE(any.assign(fat_t{.0, .1, .2, .3})); + ASSERT_FALSE(any.assign('c')); + ASSERT_EQ(any.cast(), (fat_t{.0, .1, .2, .3})); + ASSERT_EQ(addr, std::as_const(any).data()); +} + +TEST_F(MetaAny, NoSBOTransferConstValue) { + const fat_t instance{.0, .1, .2, .3}; + entt::meta_any any{fat_t{.1, .2, .3, .4}}; + + const void *addr = std::as_const(any).data(); + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast(), (fat_t{.1, .2, .3, .4})); + + ASSERT_TRUE(any.assign(entt::forward_as_meta(instance))); + ASSERT_EQ(any.cast(), (fat_t{.0, .1, .2, .3})); + ASSERT_EQ(addr, std::as_const(any).data()); +} + +TEST_F(MetaAny, NoSBOConvertTransferValue) { + entt::meta_any any{empty_t{}}; + + const void *addr = std::as_const(any).data(); + + ASSERT_TRUE(any); + ASSERT_TRUE(any.assign(fat_t{.0, .1, .2, .3})); + ASSERT_FALSE(any.assign('c')); + ASSERT_EQ(addr, std::as_const(any).data()); +} + +TEST_F(MetaAny, NoSBOAsRefTransferValue) { + fat_t instance{.1, .2, .3, .4}; + entt::meta_any any{entt::forward_as_meta(instance)}; + + const void *addr = std::as_const(any).data(); + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast(), (fat_t{.1, .2, .3, .4})); + + ASSERT_TRUE(any.assign(fat_t{.0, .1, .2, .3})); + ASSERT_FALSE(any.assign('c')); + ASSERT_EQ(any.cast(), (fat_t{.0, .1, .2, .3})); + ASSERT_EQ(instance, (fat_t{.0, .1, .2, .3})); + ASSERT_EQ(addr, std::as_const(any).data()); +} + +TEST_F(MetaAny, NoSBOAsConstRefTransferValue) { + const fat_t instance{.1, .2, .3, .4}; + entt::meta_any any{entt::forward_as_meta(instance)}; + + const void *addr = std::as_const(any).data(); + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast(), (fat_t{.1, .2, .3, .4})); + + ASSERT_FALSE(any.assign(fat_t{.0, .1, .2, .3})); + ASSERT_FALSE(any.assign('c')); + ASSERT_EQ(any.cast(), (fat_t{.1, .2, .3, .4})); + ASSERT_EQ(instance, (fat_t{.1, .2, .3, .4})); + ASSERT_EQ(addr, std::as_const(any).data()); +} + +TEST_F(MetaAny, VoidInPlaceTypeConstruction) { + entt::meta_any any{std::in_place_type}; + + ASSERT_TRUE(any); + ASSERT_TRUE(any.owner()); + ASSERT_FALSE(any.try_cast()); + ASSERT_EQ(any.data(), nullptr); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any, entt::meta_any{std::in_place_type}); + ASSERT_NE(entt::meta_any{3}, any); +} + +TEST_F(MetaAny, VoidCopyConstruction) { + entt::meta_any any{std::in_place_type}; + entt::meta_any other{any}; + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(other, entt::meta_any{std::in_place_type}); +} + +TEST_F(MetaAny, VoidCopyAssignment) { + entt::meta_any any{std::in_place_type}; + entt::meta_any other{std::in_place_type}; + + other = any; + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(other, entt::meta_any{std::in_place_type}); +} + +TEST_F(MetaAny, VoidMoveConstruction) { + entt::meta_any any{std::in_place_type}; + entt::meta_any other{std::move(any)}; + + ASSERT_FALSE(any); + ASSERT_TRUE(other); + ASSERT_EQ(other.type(), entt::resolve()); + ASSERT_EQ(other, entt::meta_any{std::in_place_type}); +} + +TEST_F(MetaAny, VoidMoveAssignment) { + entt::meta_any any{std::in_place_type}; + entt::meta_any other{std::in_place_type}; + + other = std::move(any); + + ASSERT_FALSE(any); + ASSERT_TRUE(other); + ASSERT_EQ(other.type(), entt::resolve()); + ASSERT_EQ(other, entt::meta_any{std::in_place_type}); +} + +TEST_F(MetaAny, SBOMoveInvalidate) { + entt::meta_any any{42}; + entt::meta_any other{std::move(any)}; + entt::meta_any valid = std::move(other); + + ASSERT_FALSE(any); + ASSERT_FALSE(other); + ASSERT_TRUE(valid); +} + +TEST_F(MetaAny, NoSBOMoveInvalidate) { + fat_t instance{.1, .2, .3, .4}; + entt::meta_any any{instance}; + entt::meta_any other{std::move(any)}; + entt::meta_any valid = std::move(other); + + ASSERT_FALSE(any); + ASSERT_FALSE(other); + ASSERT_TRUE(valid); +} + +TEST_F(MetaAny, VoidMoveInvalidate) { + entt::meta_any any{std::in_place_type}; + entt::meta_any other{std::move(any)}; + entt::meta_any valid = std::move(other); + + ASSERT_FALSE(any); + ASSERT_FALSE(other); + ASSERT_TRUE(valid); +} + +TEST_F(MetaAny, SBODestruction) { + { + entt::meta_any any{std::in_place_type}; + any.emplace(); + any = empty_t{}; + entt::meta_any other{std::move(any)}; + any = std::move(other); + } + + ASSERT_EQ(empty_t::destroy_counter, 3); + ASSERT_EQ(empty_t::destructor_counter, 6); +} + +TEST_F(MetaAny, NoSBODestruction) { + { + entt::meta_any any{std::in_place_type, 1., 2., 3., 4.}; + any.emplace(1., 2., 3., 4.); + any = fat_t{1., 2., 3., 4.}; + entt::meta_any other{std::move(any)}; + any = std::move(other); + } + + ASSERT_EQ(fat_t::destroy_counter, 3); + ASSERT_EQ(empty_t::destructor_counter, 4); +} + +TEST_F(MetaAny, VoidDestruction) { + // just let asan tell us if everything is ok here + [[maybe_unused]] entt::meta_any any{std::in_place_type}; +} + +TEST_F(MetaAny, Emplace) { + entt::meta_any any{}; + any.emplace(42); + + ASSERT_TRUE(any); + ASSERT_FALSE(any.try_cast()); + ASSERT_EQ(any.cast(), 42); + ASSERT_NE(any.data(), nullptr); + ASSERT_EQ(any, (entt::meta_any{std::in_place_type, 42})); + ASSERT_EQ(any, entt::meta_any{42}); + ASSERT_NE(entt::meta_any{3}, any); +} + +TEST_F(MetaAny, EmplaceVoid) { + entt::meta_any any{}; + any.emplace(); + + ASSERT_TRUE(any); + ASSERT_EQ(any.data(), nullptr); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any, (entt::meta_any{std::in_place_type})); +} + +TEST_F(MetaAny, Reset) { + entt::meta_any any{42}; + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); + + any.reset(); + + ASSERT_FALSE(any); + ASSERT_EQ(any.type(), entt::meta_type{}); +} + +TEST_F(MetaAny, SBOSwap) { + entt::meta_any lhs{'c'}; + entt::meta_any rhs{42}; + + std::swap(lhs, rhs); + + ASSERT_FALSE(lhs.try_cast()); + ASSERT_EQ(lhs.cast(), 42); + ASSERT_FALSE(rhs.try_cast()); + ASSERT_EQ(rhs.cast(), 'c'); +} + +TEST_F(MetaAny, NoSBOSwap) { + entt::meta_any lhs{fat_t{.1, .2, .3, .4}}; + entt::meta_any rhs{fat_t{.4, .3, .2, .1}}; + + std::swap(lhs, rhs); + + ASSERT_EQ(lhs.cast(), (fat_t{.4, .3, .2, .1})); + ASSERT_EQ(rhs.cast(), (fat_t{.1, .2, .3, .4})); +} + +TEST_F(MetaAny, VoidSwap) { + entt::meta_any lhs{std::in_place_type}; + entt::meta_any rhs{std::in_place_type}; + const auto *pre = lhs.data(); + + std::swap(lhs, rhs); + + ASSERT_EQ(pre, lhs.data()); +} + +TEST_F(MetaAny, SBOWithNoSBOSwap) { + entt::meta_any lhs{fat_t{.1, .2, .3, .4}}; + entt::meta_any rhs{'c'}; + + std::swap(lhs, rhs); + + ASSERT_FALSE(lhs.try_cast()); + ASSERT_EQ(lhs.cast(), 'c'); + ASSERT_FALSE(rhs.try_cast()); + ASSERT_EQ(rhs.cast(), (fat_t{.1, .2, .3, .4})); +} + +TEST_F(MetaAny, SBOWithEmptySwap) { + entt::meta_any lhs{'c'}; + entt::meta_any rhs{}; + + std::swap(lhs, rhs); + + ASSERT_FALSE(lhs); + ASSERT_EQ(rhs.cast(), 'c'); + + std::swap(lhs, rhs); + + ASSERT_FALSE(rhs); + ASSERT_EQ(lhs.cast(), 'c'); +} + +TEST_F(MetaAny, SBOWithVoidSwap) { + entt::meta_any lhs{'c'}; + entt::meta_any rhs{std::in_place_type}; + + std::swap(lhs, rhs); + + ASSERT_EQ(lhs.type(), entt::resolve()); + ASSERT_EQ(rhs.cast(), 'c'); +} + +TEST_F(MetaAny, NoSBOWithEmptySwap) { + entt::meta_any lhs{fat_t{.1, .2, .3, .4}}; + entt::meta_any rhs{}; + + std::swap(lhs, rhs); + + ASSERT_FALSE(lhs); + ASSERT_EQ(rhs.cast(), (fat_t{.1, .2, .3, .4})); + + std::swap(lhs, rhs); + + ASSERT_FALSE(rhs); + ASSERT_EQ(lhs.cast(), (fat_t{.1, .2, .3, .4})); +} + +TEST_F(MetaAny, NoSBOWithVoidSwap) { + entt::meta_any lhs{fat_t{.1, .2, .3, .4}}; + entt::meta_any rhs{std::in_place_type}; + + std::swap(lhs, rhs); + + ASSERT_EQ(lhs.type(), entt::resolve()); + ASSERT_EQ(rhs.cast(), (fat_t{.1, .2, .3, .4})); + + std::swap(lhs, rhs); + + ASSERT_EQ(rhs.type(), entt::resolve()); + ASSERT_EQ(lhs.cast(), (fat_t{.1, .2, .3, .4})); +} + +TEST_F(MetaAny, AsRef) { + entt::meta_any any{42}; + auto ref = any.as_ref(); + auto cref = std::as_const(any).as_ref(); + + ASSERT_EQ(any.try_cast(), any.data()); + ASSERT_EQ(ref.try_cast(), any.data()); + ASSERT_EQ(cref.try_cast(), nullptr); + + ASSERT_EQ(any.try_cast(), any.data()); + ASSERT_EQ(ref.try_cast(), any.data()); + ASSERT_EQ(cref.try_cast(), any.data()); + + ASSERT_EQ(any.cast(), 42); + ASSERT_EQ(ref.cast(), 42); + ASSERT_EQ(cref.cast(), 42); + + ASSERT_EQ(any.cast(), 42); + ASSERT_EQ(ref.cast(), 42); + ASSERT_EQ(cref.cast(), 42); + + ASSERT_EQ(any.cast(), 42); + ASSERT_EQ(any.cast(), 42); + ASSERT_EQ(ref.cast(), 42); + ASSERT_EQ(ref.cast(), 42); + ASSERT_EQ(cref.cast(), 42); + + any.cast() = 3; + + ASSERT_EQ(any.cast(), 3); + ASSERT_EQ(ref.cast(), 3); + ASSERT_EQ(cref.cast(), 3); + + std::swap(ref, cref); + + ASSERT_EQ(ref.try_cast(), nullptr); + ASSERT_EQ(cref.try_cast(), any.data()); + + ref = ref.as_ref(); + cref = std::as_const(cref).as_ref(); + + ASSERT_EQ(ref.try_cast(), nullptr); + ASSERT_EQ(cref.try_cast(), nullptr); + ASSERT_EQ(ref.try_cast(), any.data()); + ASSERT_EQ(cref.try_cast(), any.data()); + + ASSERT_EQ(ref.cast(), 3); + ASSERT_EQ(cref.cast(), 3); + + ref = 42; + cref = 42; + + ASSERT_NE(ref.try_cast(), nullptr); + ASSERT_NE(cref.try_cast(), nullptr); + ASSERT_EQ(ref.cast(), 42); + ASSERT_EQ(cref.cast(), 42); + ASSERT_EQ(ref.cast(), 42); + ASSERT_EQ(cref.cast(), 42); + ASSERT_NE(ref.try_cast(), any.data()); + ASSERT_NE(cref.try_cast(), any.data()); + + any.emplace(); + ref = any.as_ref(); + cref = std::as_const(any).as_ref(); + + ASSERT_TRUE(any); + ASSERT_FALSE(ref); + ASSERT_FALSE(cref); +} + +TEST_F(MetaAnyDeathTest, AsRef) { + entt::meta_any any{42}; + auto cref = std::as_const(any).as_ref(); + + ASSERT_TRUE(any); + ASSERT_DEATH(cref.cast() = 3, ""); +} + +TEST_F(MetaAny, Comparable) { + entt::meta_any any{'c'}; + + ASSERT_EQ(any, any); + ASSERT_EQ(any, entt::meta_any{'c'}); + ASSERT_NE(entt::meta_any{'a'}, any); + ASSERT_NE(any, entt::meta_any{}); + + ASSERT_TRUE(any == any); + ASSERT_TRUE(any == entt::meta_any{'c'}); + ASSERT_FALSE(entt::meta_any{'a'} == any); + ASSERT_TRUE(any != entt::meta_any{'a'}); + ASSERT_TRUE(entt::meta_any{} != any); +} + +TEST_F(MetaAny, NotComparable) { + entt::meta_any any{not_comparable_t{}}; + + ASSERT_EQ(any, any); + ASSERT_NE(any, entt::meta_any{not_comparable_t{}}); + ASSERT_NE(entt::meta_any{}, any); + + ASSERT_TRUE(any == any); + ASSERT_FALSE(any == entt::meta_any{not_comparable_t{}}); + ASSERT_TRUE(entt::meta_any{} != any); +} + +TEST_F(MetaAny, CompareVoid) { + entt::meta_any any{std::in_place_type}; + + ASSERT_EQ(any, any); + ASSERT_EQ(any, entt::meta_any{std::in_place_type}); + ASSERT_NE(entt::meta_any{'a'}, any); + ASSERT_NE(any, entt::meta_any{}); + + ASSERT_TRUE(any == any); + ASSERT_TRUE(any == entt::meta_any{std::in_place_type}); + ASSERT_FALSE(entt::meta_any{'a'} == any); + ASSERT_TRUE(any != entt::meta_any{'a'}); + ASSERT_TRUE(entt::meta_any{} != any); +} + +TEST_F(MetaAny, TryCast) { + entt::meta_any any{fat_t{}}; + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.try_cast(), nullptr); + ASSERT_NE(any.try_cast(), nullptr); + ASSERT_EQ(any.try_cast(), any.data()); + ASSERT_EQ(std::as_const(any).try_cast(), any.try_cast()); + ASSERT_EQ(std::as_const(any).try_cast(), any.data()); + ASSERT_EQ(std::as_const(any).try_cast(), nullptr); +} + +TEST_F(MetaAny, Cast) { + entt::meta_any any{fat_t{}}; + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(std::as_const(any).cast(), fat_t{}); + ASSERT_EQ(any.cast(), fat_t{}); + ASSERT_EQ(any.cast(), fat_t{}); + ASSERT_EQ(any.cast(), fat_t{}); + + ASSERT_EQ(any.cast().value[0u], 0.); + + any.cast().value[0u] = 3.; + + ASSERT_EQ(any.cast().value[0u], 3.); +} + +TEST_F(MetaAny, AllowCast) { + entt::meta_any clazz{clazz_t{}}; + entt::meta_any fat{fat_t{}}; + entt::meta_any arithmetic{42}; + auto as_cref = entt::forward_as_meta(arithmetic.cast()); + + ASSERT_TRUE(clazz); + ASSERT_TRUE(fat); + ASSERT_TRUE(arithmetic); + ASSERT_TRUE(as_cref); + + ASSERT_TRUE(clazz.allow_cast()); + ASSERT_TRUE(clazz.allow_cast()); + ASSERT_TRUE(clazz.allow_cast()); + ASSERT_EQ(clazz.type(), entt::resolve()); + + ASSERT_TRUE(clazz.allow_cast()); + ASSERT_EQ(clazz.type(), entt::resolve()); + ASSERT_TRUE(clazz.allow_cast()); + ASSERT_TRUE(clazz.allow_cast()); + ASSERT_TRUE(clazz.allow_cast()); + + ASSERT_TRUE(fat.allow_cast()); + ASSERT_TRUE(fat.allow_cast()); + ASSERT_TRUE(fat.allow_cast()); + ASSERT_EQ(fat.type(), entt::resolve()); + ASSERT_FALSE(fat.allow_cast()); + + ASSERT_TRUE(std::as_const(fat).allow_cast()); + ASSERT_FALSE(std::as_const(fat).allow_cast()); + ASSERT_TRUE(std::as_const(fat).allow_cast()); + ASSERT_EQ(fat.type(), entt::resolve()); + ASSERT_FALSE(fat.allow_cast()); + + ASSERT_TRUE(arithmetic.allow_cast()); + ASSERT_TRUE(arithmetic.allow_cast()); + ASSERT_TRUE(arithmetic.allow_cast()); + ASSERT_EQ(arithmetic.type(), entt::resolve()); + ASSERT_FALSE(arithmetic.allow_cast()); + + ASSERT_TRUE(arithmetic.allow_cast()); + ASSERT_EQ(arithmetic.type(), entt::resolve()); + ASSERT_EQ(arithmetic.cast(), 42.); + + ASSERT_TRUE(arithmetic.allow_cast()); + ASSERT_EQ(arithmetic.type(), entt::resolve()); + ASSERT_EQ(arithmetic.cast(), 42.f); + + ASSERT_TRUE(as_cref.allow_cast()); + ASSERT_FALSE(as_cref.allow_cast()); + ASSERT_TRUE(as_cref.allow_cast()); + ASSERT_EQ(as_cref.type(), entt::resolve()); + ASSERT_FALSE(as_cref.allow_cast()); + + ASSERT_TRUE(as_cref.allow_cast()); + ASSERT_EQ(as_cref.type(), entt::resolve()); +} + +TEST_F(MetaAny, OpaqueAllowCast) { + entt::meta_any clazz{clazz_t{}}; + entt::meta_any fat{fat_t{}}; + entt::meta_any arithmetic{42}; + auto as_cref = entt::forward_as_meta(arithmetic.cast()); + + ASSERT_TRUE(clazz); + ASSERT_TRUE(fat); + ASSERT_TRUE(arithmetic); + ASSERT_TRUE(as_cref); + + ASSERT_TRUE(clazz.allow_cast(entt::resolve())); + ASSERT_EQ(clazz.type(), entt::resolve()); + + ASSERT_TRUE(clazz.allow_cast(entt::resolve())); + ASSERT_EQ(clazz.type(), entt::resolve()); + ASSERT_TRUE(clazz.allow_cast(entt::resolve())); + + ASSERT_TRUE(fat.allow_cast(entt::resolve())); + ASSERT_TRUE(fat.allow_cast(entt::resolve())); + ASSERT_EQ(fat.type(), entt::resolve()); + ASSERT_FALSE(fat.allow_cast(entt::resolve())); + + ASSERT_TRUE(std::as_const(fat).allow_cast(entt::resolve())); + ASSERT_TRUE(std::as_const(fat).allow_cast(entt::resolve())); + ASSERT_EQ(fat.type(), entt::resolve()); + ASSERT_FALSE(fat.allow_cast(entt::resolve())); + + ASSERT_TRUE(arithmetic.allow_cast(entt::resolve())); + ASSERT_EQ(arithmetic.type(), entt::resolve()); + ASSERT_FALSE(arithmetic.allow_cast(entt::resolve())); + + ASSERT_TRUE(arithmetic.allow_cast(entt::resolve())); + ASSERT_EQ(arithmetic.type(), entt::resolve()); + ASSERT_EQ(arithmetic.cast(), 42.); + + ASSERT_TRUE(arithmetic.allow_cast(entt::resolve())); + ASSERT_EQ(arithmetic.type(), entt::resolve()); + ASSERT_EQ(arithmetic.cast(), 42.f); + + ASSERT_TRUE(as_cref.allow_cast(entt::resolve())); + ASSERT_EQ(as_cref.type(), entt::resolve()); + ASSERT_FALSE(as_cref.allow_cast(entt::resolve())); + + ASSERT_TRUE(as_cref.allow_cast(entt::resolve())); + ASSERT_EQ(as_cref.type(), entt::resolve()); +} + +TEST_F(MetaAny, Convert) { + entt::meta_any any{clazz_t{}}; + any.cast().value = 42; + auto as_int = std::as_const(any).allow_cast(); + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_TRUE(any.allow_cast()); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_TRUE(any.allow_cast()); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), 42); + + ASSERT_TRUE(as_int); + ASSERT_EQ(as_int.type(), entt::resolve()); + ASSERT_EQ(as_int.cast(), 42); + + ASSERT_TRUE(as_int.allow_cast()); + ASSERT_EQ(as_int.type(), entt::resolve()); + ASSERT_EQ(as_int.cast(), char{42}); +} + +TEST_F(MetaAny, ArithmeticConversion) { + entt::meta_any any{'c'}; + + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), 'c'); + + ASSERT_TRUE(any.allow_cast()); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), static_cast('c')); + + any = 3.1; + + ASSERT_TRUE(any.allow_cast(entt::resolve())); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), 3); + + ASSERT_TRUE(any.allow_cast()); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), 3.f); + + any = static_cast('c'); + + ASSERT_TRUE(any.allow_cast()); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), 'c'); +} + +TEST_F(MetaAny, EnumConversion) { + entt::meta_any any{enum_class::foo}; + + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), enum_class::foo); + + ASSERT_TRUE(any.allow_cast()); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), 0.); + + any = enum_class::bar; + + ASSERT_TRUE(any.allow_cast(entt::resolve())); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), 42); + + ASSERT_TRUE(any.allow_cast()); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), enum_class::bar); + + any = 0; + + ASSERT_TRUE(any.allow_cast(entt::resolve())); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), enum_class::foo); +} + +TEST_F(MetaAny, UnmanageableType) { + unmanageable_t instance; + auto any = entt::forward_as_meta(instance); + entt::meta_any other = any.as_ref(); + + std::swap(any, other); + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_NE(any.data(), nullptr); + ASSERT_EQ(any.try_cast(), nullptr); + ASSERT_NE(any.try_cast(), nullptr); + + ASSERT_TRUE(any.allow_cast()); + ASSERT_FALSE(any.allow_cast()); + + ASSERT_TRUE(std::as_const(any).allow_cast()); + ASSERT_FALSE(std::as_const(any).allow_cast()); +} + +TEST_F(MetaAny, Invoke) { + using namespace entt::literals; + + clazz_t instance; + auto any = entt::forward_as_meta(instance); + + ASSERT_TRUE(any.invoke("func"_hs)); + ASSERT_TRUE(any.invoke("member"_hs, 42)); + ASSERT_FALSE(std::as_const(any).invoke("member"_hs, 42)); + ASSERT_FALSE(std::as_const(any).as_ref().invoke("member"_hs, 42)); + ASSERT_FALSE(any.invoke("non_existent"_hs, 42)); + + ASSERT_EQ(clazz_t::c, 'd'); + ASSERT_EQ(instance.value, 42); +} + +TEST_F(MetaAny, SetGet) { + using namespace entt::literals; + + clazz_t instance; + auto any = entt::forward_as_meta(instance); + + ASSERT_TRUE(any.set("value"_hs, 42)); + + const auto value = std::as_const(any).get("value"_hs); + + ASSERT_TRUE(value); + ASSERT_EQ(value, any.get("value"_hs)); + ASSERT_EQ(value, std::as_const(any).as_ref().get("value"_hs)); + ASSERT_NE(value.try_cast(), nullptr); + ASSERT_EQ(value.cast(), 42); + ASSERT_EQ(instance.value, 42); + + ASSERT_FALSE(any.set("non_existent"_hs, 42)); + ASSERT_FALSE(any.get("non_existent"_hs)); +} + +TEST_F(MetaAny, MakeMeta) { + int value = 42; + auto any = entt::make_meta(value); + auto ref = entt::make_meta(value); + + ASSERT_TRUE(any); + ASSERT_TRUE(ref); + + ASSERT_EQ(any.cast(), 42); + ASSERT_EQ(ref.cast(), 42); + + ASSERT_NE(any.data(), &value); + ASSERT_EQ(ref.data(), &value); +} + +TEST_F(MetaAny, ForwardAsMeta) { + int value = 42; + auto any = entt::forward_as_meta(std::move(value)); + auto ref = entt::forward_as_meta(value); + auto cref = entt::forward_as_meta(std::as_const(value)); + + ASSERT_TRUE(any); + ASSERT_TRUE(ref); + ASSERT_TRUE(cref); + + ASSERT_NE(any.try_cast(), nullptr); + ASSERT_NE(ref.try_cast(), nullptr); + ASSERT_EQ(cref.try_cast(), nullptr); + + ASSERT_EQ(any.cast(), 42); + ASSERT_EQ(ref.cast(), 42); + ASSERT_EQ(cref.cast(), 42); + + ASSERT_NE(any.data(), &value); + ASSERT_EQ(ref.data(), &value); +} diff --git a/modules/entt/test/entt/meta/meta_base.cpp b/modules/entt/test/entt/meta/meta_base.cpp new file mode 100644 index 0000000..d140e34 --- /dev/null +++ b/modules/entt/test/entt/meta/meta_base.cpp @@ -0,0 +1,195 @@ +#include +#include +#include +#include +#include +#include +#include + +struct base_1_t { + base_1_t() = default; + int value_1{}; +}; + +struct base_2_t { + base_2_t() = default; + + operator int() const { + return value_2; + } + + int value_2{}; +}; + +struct base_3_t: base_2_t { + base_3_t() = default; + int value_3{}; +}; + +struct derived_t: base_1_t, base_3_t { + derived_t() = default; + int value{}; +}; + +struct MetaBase: ::testing::Test { + void SetUp() override { + using namespace entt::literals; + + entt::meta() + .data<&base_1_t::value_1>("value_1"_hs); + + entt::meta() + .conv() + .data<&base_2_t::value_2>("value_2"_hs); + + entt::meta() + .base() + .data<&base_3_t::value_3>("value_3"_hs); + + entt::meta() + .type("derived"_hs) + .base() + .base() + .data<&derived_t::value>("value"_hs); + } + + void TearDown() override { + entt::meta_reset(); + } +}; + +TEST_F(MetaBase, Functionalities) { + auto any = entt::resolve().construct(); + any.cast().value_1 = 42; + auto as_derived = any.as_ref(); + + ASSERT_TRUE(any.allow_cast()); + + ASSERT_FALSE(any.allow_cast()); + ASSERT_FALSE(as_derived.allow_cast()); + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast().value_1, as_derived.cast().value_1); + + any.cast().value_1 = 3; + + ASSERT_EQ(any.cast().value_1, as_derived.cast().value_1); +} + +TEST_F(MetaBase, SetGetWithMutatingThis) { + using namespace entt::literals; + + derived_t instance; + auto any = entt::forward_as_meta(instance); + auto as_cref = std::as_const(any).as_ref(); + + ASSERT_NE(static_cast(static_cast(&instance)), static_cast(static_cast(&instance))); + ASSERT_NE(static_cast(static_cast(&instance)), static_cast(static_cast(&instance))); + ASSERT_EQ(static_cast(static_cast(&instance)), static_cast(static_cast(&instance))); + ASSERT_EQ(static_cast(&instance), static_cast(static_cast(&instance))); + + ASSERT_TRUE(any.set("value"_hs, 42)); + ASSERT_TRUE(any.set("value_1"_hs, 1)); + ASSERT_TRUE(any.set("value_2"_hs, 2)); + ASSERT_TRUE(any.set("value_3"_hs, 3)); + + ASSERT_FALSE(as_cref.set("value"_hs, 0)); + ASSERT_FALSE(as_cref.set("value_1"_hs, 0)); + ASSERT_FALSE(as_cref.set("value_2"_hs, 0)); + ASSERT_FALSE(as_cref.set("value_3"_hs, 0)); + + ASSERT_EQ(any.get("value"_hs).cast(), 42); + ASSERT_EQ(any.get("value_1"_hs).cast(), 1); + ASSERT_EQ(any.get("value_2"_hs).cast(), 2); + ASSERT_EQ(any.get("value_3"_hs).cast(), 3); + + ASSERT_EQ(as_cref.get("value"_hs).cast(), 42); + ASSERT_EQ(as_cref.get("value_1"_hs).cast(), 1); + ASSERT_EQ(as_cref.get("value_2"_hs).cast(), 2); + ASSERT_EQ(as_cref.get("value_3"_hs).cast(), 3); + + ASSERT_EQ(instance.value, 42); + ASSERT_EQ(instance.value_1, 1); + ASSERT_EQ(instance.value_2, 2); + ASSERT_EQ(instance.value_3, 3); +} + +TEST_F(MetaBase, ConvWithMutatingThis) { + entt::meta_any any{derived_t{}}; + auto as_cref = std::as_const(any).as_ref(); + any.cast().value_2 = 42; + + auto conv = std::as_const(any).allow_cast(); + auto from_cref = std::as_const(as_cref).allow_cast(); + + ASSERT_TRUE(conv); + ASSERT_TRUE(from_cref); + ASSERT_EQ(conv.cast(), 42); + ASSERT_EQ(from_cref.cast(), 42); + + ASSERT_TRUE(any.allow_cast()); + ASSERT_TRUE(as_cref.allow_cast()); + ASSERT_EQ(any.cast(), 42); + ASSERT_EQ(as_cref.cast(), 42); +} + +TEST_F(MetaBase, OpaqueConvWithMutatingThis) { + entt::meta_any any{derived_t{}}; + auto as_cref = std::as_const(any).as_ref(); + any.cast().value_2 = 42; + + auto conv = std::as_const(any).allow_cast(entt::resolve()); + auto from_cref = std::as_const(as_cref).allow_cast(entt::resolve()); + + ASSERT_TRUE(conv); + ASSERT_TRUE(from_cref); + ASSERT_EQ(conv.cast(), 42); + ASSERT_EQ(from_cref.cast(), 42); + + ASSERT_TRUE(any.allow_cast(entt::resolve())); + ASSERT_TRUE(as_cref.allow_cast(entt::resolve())); + ASSERT_EQ(any.cast(), 42); + ASSERT_EQ(as_cref.cast(), 42); +} + +TEST_F(MetaBase, AssignWithMutatingThis) { + using namespace entt::literals; + + entt::meta_any dst{base_2_t{}}; + entt::meta_any src{derived_t{}}; + + dst.cast().value_2 = 0; + src.cast().value_2 = 42; + + ASSERT_TRUE(dst.assign(src)); + ASSERT_EQ(dst.get("value_2"_hs).cast(), 42); +} + +TEST_F(MetaBase, TransferWithMutatingThis) { + using namespace entt::literals; + + entt::meta_any dst{base_2_t{}}; + entt::meta_any src{derived_t{}}; + + dst.cast().value_2 = 0; + src.cast().value_2 = 42; + + ASSERT_TRUE(dst.assign(std::move(src))); + ASSERT_EQ(dst.get("value_2"_hs).cast(), 42); +} + +TEST_F(MetaBase, ReRegistration) { + SetUp(); + + auto *node = entt::internal::meta_node::resolve(); + + ASSERT_NE(node->base, nullptr); + ASSERT_NE(node->base->type->base, nullptr); + ASSERT_EQ(node->base->type->base->next, nullptr); + ASSERT_EQ(node->base->type->base->type->base, nullptr); + + ASSERT_NE(node->base->next, nullptr); + ASSERT_EQ(node->base->next->type->base, nullptr); + + ASSERT_EQ(node->base->next->next, nullptr); +} diff --git a/modules/entt/test/entt/meta/meta_container.cpp b/modules/entt/test/entt/meta/meta_container.cpp new file mode 100644 index 0000000..b859611 --- /dev/null +++ b/modules/entt/test/entt/meta/meta_container.cpp @@ -0,0 +1,639 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct invalid_type {}; + +struct MetaContainer: ::testing::Test { + void SetUp() override { + using namespace entt::literals; + + entt::meta() + .type("double"_hs); + + entt::meta() + .type("int"_hs); + } + + void TearDown() override { + entt::meta_reset(); + } +}; + +using MetaContainerDeathTest = MetaContainer; + +TEST_F(MetaContainer, InvalidContainer) { + ASSERT_FALSE(entt::meta_any{42}.as_sequence_container()); + ASSERT_FALSE(entt::meta_any{42}.as_associative_container()); + + ASSERT_FALSE((entt::meta_any{std::map{}}.as_sequence_container())); + ASSERT_FALSE(entt::meta_any{std::vector{}}.as_associative_container()); +} + +TEST_F(MetaContainer, EmptySequenceContainer) { + entt::meta_sequence_container container{}; + + ASSERT_FALSE(container); + + entt::meta_any any{std::vector{}}; + container = any.as_sequence_container(); + + ASSERT_TRUE(container); +} + +TEST_F(MetaContainer, EmptyAssociativeContainer) { + entt::meta_associative_container container{}; + + ASSERT_FALSE(container); + + entt::meta_any any{std::map{}}; + container = any.as_associative_container(); + + ASSERT_TRUE(container); +} + +TEST_F(MetaContainer, SequenceContainerIterator) { + std::vector vec{2, 3, 4}; + auto any = entt::forward_as_meta(vec); + entt::meta_sequence_container::iterator first{}; + auto view = any.as_sequence_container(); + + ASSERT_FALSE(first); + + first = view.begin(); + const auto last = view.end(); + + ASSERT_TRUE(first); + ASSERT_TRUE(last); + + ASSERT_FALSE(first == last); + ASSERT_TRUE(first != last); + + ASSERT_EQ((first++)->cast(), 2); + ASSERT_EQ((++first)->cast(), 4); + + ASSERT_NE(first++, last); + ASSERT_TRUE(first == last); + ASSERT_FALSE(first != last); + ASSERT_EQ(first--, last); + + ASSERT_EQ((first--)->cast(), 4); + ASSERT_EQ((--first)->cast(), 2); +} + +TEST_F(MetaContainer, AssociativeContainerIterator) { + std::map map{{2, 'c'}, {3, 'd'}, {4, 'e'}}; + auto any = entt::forward_as_meta(map); + entt::meta_associative_container::iterator first{}; + auto view = any.as_associative_container(); + + ASSERT_FALSE(first); + + first = view.begin(); + const auto last = view.end(); + + ASSERT_TRUE(first); + ASSERT_TRUE(last); + + ASSERT_FALSE(first == last); + ASSERT_TRUE(first != last); + + ASSERT_NE(first, last); + ASSERT_EQ((first++)->first.cast(), 2); + ASSERT_EQ((++first)->second.cast(), 'e'); + ASSERT_NE(first++, last); + ASSERT_EQ(first, last); + + ASSERT_TRUE(first == last); + ASSERT_FALSE(first != last); +} + +TEST_F(MetaContainer, StdVector) { + std::vector vec{}; + auto any = entt::forward_as_meta(vec); + auto view = any.as_sequence_container(); + + ASSERT_TRUE(view); + ASSERT_EQ(view.value_type(), entt::resolve()); + + ASSERT_EQ(view.size(), 0u); + ASSERT_EQ(view.begin(), view.end()); + ASSERT_TRUE(view.resize(3u)); + ASSERT_EQ(view.size(), 3u); + ASSERT_NE(view.begin(), view.end()); + + view[0].cast() = 2; + view[1].cast() = 3; + view[2].cast() = 4; + + ASSERT_EQ(view[1u].cast(), 3); + + auto it = view.begin(); + auto ret = view.insert(it, 0); + + ASSERT_TRUE(ret); + ASSERT_FALSE(view.insert(ret, invalid_type{})); + ASSERT_TRUE(view.insert(++ret, 1.)); + + ASSERT_EQ(view.size(), 5u); + ASSERT_EQ(view.begin()->cast(), 0); + ASSERT_EQ((++view.begin())->cast(), 1); + + ret = view.insert(view.end(), 42); + + ASSERT_TRUE(ret); + ASSERT_EQ(*ret, 42); + + it = view.begin(); + ret = view.erase(it); + + ASSERT_TRUE(ret); + ASSERT_EQ(view.size(), 5u); + ASSERT_EQ(ret->cast(), 1); + + ASSERT_TRUE(view.clear()); + ASSERT_EQ(view.size(), 0u); +} + +TEST_F(MetaContainer, StdArray) { + std::array arr{}; + auto any = entt::forward_as_meta(arr); + auto view = any.as_sequence_container(); + + ASSERT_TRUE(view); + ASSERT_EQ(view.value_type(), entt::resolve()); + + ASSERT_EQ(view.size(), 3u); + ASSERT_NE(view.begin(), view.end()); + ASSERT_FALSE(view.resize(5u)); + ASSERT_EQ(view.size(), 3u); + + view[0].cast() = 2; + view[1].cast() = 3; + view[2].cast() = 4; + + ASSERT_EQ(view[1u].cast(), 3); + + auto it = view.begin(); + auto ret = view.insert(it, 0); + + ASSERT_FALSE(ret); + ASSERT_FALSE(view.insert(it, 'c')); + ASSERT_FALSE(view.insert(++it, 1.)); + + ASSERT_EQ(view.size(), 3u); + ASSERT_EQ(view.begin()->cast(), 2); + ASSERT_EQ((++view.begin())->cast(), 3); + + it = view.begin(); + ret = view.erase(it); + + ASSERT_FALSE(ret); + ASSERT_EQ(view.size(), 3u); + ASSERT_EQ(it->cast(), 2); + + ASSERT_FALSE(view.clear()); + ASSERT_EQ(view.size(), 3u); +} + +TEST_F(MetaContainer, StdMap) { + std::map map{{2, 'c'}, {3, 'd'}, {4, 'e'}}; + auto any = entt::forward_as_meta(map); + auto view = any.as_associative_container(); + + ASSERT_TRUE(view); + ASSERT_FALSE(view.key_only()); + ASSERT_EQ(view.key_type(), entt::resolve()); + ASSERT_EQ(view.mapped_type(), entt::resolve()); + ASSERT_EQ(view.value_type(), (entt::resolve>())); + + ASSERT_EQ(view.size(), 3u); + ASSERT_NE(view.begin(), view.end()); + + ASSERT_EQ(view.find(3)->second.cast(), 'd'); + + ASSERT_FALSE(view.insert(invalid_type{}, 'a')); + ASSERT_FALSE(view.insert(1, invalid_type{})); + + ASSERT_TRUE(view.insert(0, 'a')); + ASSERT_TRUE(view.insert(1., static_cast('b'))); + + ASSERT_EQ(view.size(), 5u); + ASSERT_EQ(view.find(0)->second.cast(), 'a'); + ASSERT_EQ(view.find(1.)->second.cast(), 'b'); + + ASSERT_FALSE(view.erase(invalid_type{})); + ASSERT_FALSE(view.find(invalid_type{})); + ASSERT_EQ(view.size(), 5u); + + ASSERT_TRUE(view.erase(0)); + ASSERT_EQ(view.size(), 4u); + ASSERT_EQ(view.find(0), view.end()); + + view.find(1.)->second.cast() = 'f'; + + ASSERT_EQ(view.find(1.f)->second.cast(), 'f'); + + ASSERT_TRUE(view.erase(1.)); + ASSERT_TRUE(view.clear()); + ASSERT_EQ(view.size(), 0u); +} + +TEST_F(MetaContainer, StdSet) { + std::set set{2, 3, 4}; + auto any = entt::forward_as_meta(set); + auto view = any.as_associative_container(); + + ASSERT_TRUE(view); + ASSERT_TRUE(view.key_only()); + ASSERT_EQ(view.key_type(), entt::resolve()); + ASSERT_EQ(view.mapped_type(), entt::meta_type{}); + ASSERT_EQ(view.value_type(), entt::resolve()); + + ASSERT_EQ(view.size(), 3u); + ASSERT_NE(view.begin(), view.end()); + + ASSERT_EQ(view.find(3)->first.cast(), 3); + + ASSERT_FALSE(view.insert(invalid_type{})); + + ASSERT_TRUE(view.insert(.0)); + ASSERT_TRUE(view.insert(1)); + + ASSERT_EQ(view.size(), 5u); + ASSERT_EQ(view.find(0)->first.cast(), 0); + ASSERT_EQ(view.find(1.)->first.cast(), 1); + + ASSERT_FALSE(view.erase(invalid_type{})); + ASSERT_FALSE(view.find(invalid_type{})); + ASSERT_EQ(view.size(), 5u); + + ASSERT_TRUE(view.erase(0)); + ASSERT_EQ(view.size(), 4u); + ASSERT_EQ(view.find(0), view.end()); + + ASSERT_EQ(view.find(1.f)->first.try_cast(), nullptr); + ASSERT_NE(view.find(1.)->first.try_cast(), nullptr); + ASSERT_EQ(view.find(true)->first.cast(), 1); + + ASSERT_TRUE(view.erase(1.)); + ASSERT_TRUE(view.clear()); + ASSERT_EQ(view.size(), 0u); +} + +TEST_F(MetaContainer, DenseMap) { + entt::dense_map map{}; + auto any = entt::forward_as_meta(map); + auto view = any.as_associative_container(); + + map.emplace(2, 'c'); + map.emplace(3, 'd'); + map.emplace(4, '3'); + + ASSERT_TRUE(view); + ASSERT_FALSE(view.key_only()); + ASSERT_EQ(view.key_type(), entt::resolve()); + ASSERT_EQ(view.mapped_type(), entt::resolve()); + ASSERT_EQ(view.value_type(), (entt::resolve>())); + + ASSERT_EQ(view.size(), 3u); + ASSERT_NE(view.begin(), view.end()); + + ASSERT_EQ(view.find(3)->second.cast(), 'd'); + + ASSERT_FALSE(view.insert(invalid_type{}, 'a')); + ASSERT_FALSE(view.insert(1, invalid_type{})); + + ASSERT_TRUE(view.insert(0, 'a')); + ASSERT_TRUE(view.insert(1., static_cast('b'))); + + ASSERT_EQ(view.size(), 5u); + ASSERT_EQ(view.find(0)->second.cast(), 'a'); + ASSERT_EQ(view.find(1.)->second.cast(), 'b'); + + ASSERT_FALSE(view.erase(invalid_type{})); + ASSERT_FALSE(view.find(invalid_type{})); + ASSERT_EQ(view.size(), 5u); + + ASSERT_TRUE(view.erase(0)); + ASSERT_EQ(view.size(), 4u); + ASSERT_EQ(view.find(0), view.end()); + + view.find(1.)->second.cast() = 'f'; + + ASSERT_EQ(view.find(1.f)->second.cast(), 'f'); + + ASSERT_TRUE(view.erase(1.)); + ASSERT_TRUE(view.clear()); + ASSERT_EQ(view.size(), 0u); +} + +TEST_F(MetaContainer, DenseSet) { + entt::dense_set set{}; + auto any = entt::forward_as_meta(set); + auto view = any.as_associative_container(); + + set.emplace(2); + set.emplace(3); + set.emplace(4); + + ASSERT_TRUE(view); + ASSERT_TRUE(view.key_only()); + ASSERT_EQ(view.key_type(), entt::resolve()); + ASSERT_EQ(view.mapped_type(), entt::meta_type{}); + ASSERT_EQ(view.value_type(), entt::resolve()); + + ASSERT_EQ(view.size(), 3u); + ASSERT_NE(view.begin(), view.end()); + + ASSERT_EQ(view.find(3)->first.cast(), 3); + + ASSERT_FALSE(view.insert(invalid_type{})); + + ASSERT_TRUE(view.insert(.0)); + ASSERT_TRUE(view.insert(1)); + + ASSERT_EQ(view.size(), 5u); + ASSERT_EQ(view.find(0)->first.cast(), 0); + ASSERT_EQ(view.find(1.)->first.cast(), 1); + + ASSERT_FALSE(view.erase(invalid_type{})); + ASSERT_FALSE(view.find(invalid_type{})); + ASSERT_EQ(view.size(), 5u); + + ASSERT_TRUE(view.erase(0)); + ASSERT_EQ(view.size(), 4u); + ASSERT_EQ(view.find(0), view.end()); + + ASSERT_EQ(view.find(1.f)->first.try_cast(), nullptr); + ASSERT_NE(view.find(1.)->first.try_cast(), nullptr); + ASSERT_EQ(view.find(true)->first.cast(), 1); + + ASSERT_TRUE(view.erase(1.)); + ASSERT_TRUE(view.clear()); + ASSERT_EQ(view.size(), 0u); +} + +TEST_F(MetaContainer, ConstSequenceContainer) { + std::vector vec{}; + auto any = entt::forward_as_meta(std::as_const(vec)); + auto view = any.as_sequence_container(); + + ASSERT_TRUE(view); + ASSERT_EQ(view.value_type(), entt::resolve()); + + ASSERT_EQ(view.size(), 0u); + ASSERT_EQ(view.begin(), view.end()); + ASSERT_FALSE(view.resize(3u)); + ASSERT_EQ(view.size(), 0u); + ASSERT_EQ(view.begin(), view.end()); + + vec.push_back(42); + + ASSERT_EQ(view.size(), 1u); + ASSERT_NE(view.begin(), view.end()); + ASSERT_EQ(view[0].cast(), 42); + + auto it = view.begin(); + auto ret = view.insert(it, 0); + + ASSERT_FALSE(ret); + ASSERT_EQ(view.size(), 1u); + ASSERT_EQ(it->cast(), 42); + ASSERT_EQ(++it, view.end()); + + it = view.begin(); + ret = view.erase(it); + + ASSERT_FALSE(ret); + ASSERT_EQ(view.size(), 1u); + + ASSERT_FALSE(view.clear()); + ASSERT_EQ(view.size(), 1u); +} + +TEST_F(MetaContainerDeathTest, ConstSequenceContainer) { + std::vector vec{}; + auto any = entt::forward_as_meta(std::as_const(vec)); + auto view = any.as_sequence_container(); + + ASSERT_TRUE(view); + ASSERT_DEATH(view[0].cast() = 2, ""); +} + +TEST_F(MetaContainer, ConstKeyValueAssociativeContainer) { + std::map map{}; + auto any = entt::forward_as_meta(std::as_const(map)); + auto view = any.as_associative_container(); + + ASSERT_TRUE(view); + ASSERT_FALSE(view.key_only()); + ASSERT_EQ(view.key_type(), entt::resolve()); + ASSERT_EQ(view.mapped_type(), entt::resolve()); + ASSERT_EQ(view.value_type(), (entt::resolve>())); + + ASSERT_EQ(view.size(), 0u); + ASSERT_EQ(view.begin(), view.end()); + + map[2] = 'c'; + + ASSERT_EQ(view.size(), 1u); + ASSERT_NE(view.begin(), view.end()); + ASSERT_EQ(view.find(2)->second.cast(), 'c'); + + ASSERT_FALSE(view.insert(0, 'a')); + ASSERT_EQ(view.size(), 1u); + ASSERT_EQ(view.find(0), view.end()); + ASSERT_EQ(view.find(2)->second.cast(), 'c'); + + ASSERT_FALSE(view.erase(2)); + ASSERT_EQ(view.size(), 1u); + ASSERT_NE(view.find(2), view.end()); + + ASSERT_FALSE(view.clear()); + ASSERT_EQ(view.size(), 1u); +} + +TEST_F(MetaContainerDeathTest, ConstKeyValueAssociativeContainer) { + std::map map{}; + auto any = entt::forward_as_meta(std::as_const(map)); + auto view = any.as_associative_container(); + + ASSERT_TRUE(view); + ASSERT_DEATH(view.find(2)->second.cast() = 'a', ""); +} + +TEST_F(MetaContainer, ConstKeyOnlyAssociativeContainer) { + std::set set{}; + auto any = entt::forward_as_meta(std::as_const(set)); + auto view = any.as_associative_container(); + + ASSERT_TRUE(view); + ASSERT_TRUE(view.key_only()); + ASSERT_EQ(view.key_type(), entt::resolve()); + ASSERT_EQ(view.mapped_type(), entt::meta_type{}); + ASSERT_EQ(view.value_type(), (entt::resolve())); + + ASSERT_EQ(view.size(), 0u); + ASSERT_EQ(view.begin(), view.end()); + + set.insert(2); + + ASSERT_EQ(view.size(), 1u); + ASSERT_NE(view.begin(), view.end()); + + ASSERT_EQ(view.find(2)->first.try_cast(), nullptr); + ASSERT_NE(view.find(2)->first.try_cast(), nullptr); + ASSERT_EQ(view.find(2)->first.cast(), 2); + ASSERT_EQ(view.find(2)->first.cast(), 2); + + ASSERT_FALSE(view.insert(0)); + ASSERT_EQ(view.size(), 1u); + ASSERT_EQ(view.find(0), view.end()); + ASSERT_EQ(view.find(2)->first.cast(), 2); + + ASSERT_FALSE(view.erase(2)); + ASSERT_EQ(view.size(), 1u); + ASSERT_NE(view.find(2), view.end()); + + ASSERT_FALSE(view.clear()); + ASSERT_EQ(view.size(), 1u); +} + +TEST_F(MetaContainer, SequenceContainerConstMetaAny) { + auto test = [](const entt::meta_any any) { + auto view = any.as_sequence_container(); + + ASSERT_TRUE(view); + ASSERT_EQ(view.value_type(), entt::resolve()); + ASSERT_EQ(view[0].cast(), 42); + }; + + std::vector vec{42}; + + test(vec); + test(entt::forward_as_meta(vec)); + test(entt::forward_as_meta(std::as_const(vec))); +} + +TEST_F(MetaContainerDeathTest, SequenceContainerConstMetaAny) { + auto test = [](const entt::meta_any any) { + auto view = any.as_sequence_container(); + + ASSERT_TRUE(view); + ASSERT_DEATH(view[0].cast() = 2, ""); + }; + + std::vector vec{42}; + + test(vec); + test(entt::forward_as_meta(vec)); + test(entt::forward_as_meta(std::as_const(vec))); +} + +TEST_F(MetaContainer, KeyValueAssociativeContainerConstMetaAny) { + auto test = [](const entt::meta_any any) { + auto view = any.as_associative_container(); + + ASSERT_TRUE(view); + ASSERT_EQ(view.value_type(), (entt::resolve>())); + ASSERT_EQ(view.find(2)->second.cast(), 'c'); + }; + + std::map map{{2, 'c'}}; + + test(map); + test(entt::forward_as_meta(map)); + test(entt::forward_as_meta(std::as_const(map))); +} + +TEST_F(MetaContainerDeathTest, KeyValueAssociativeContainerConstMetaAny) { + auto test = [](const entt::meta_any any) { + auto view = any.as_associative_container(); + + ASSERT_TRUE(view); + ASSERT_DEATH(view.find(2)->second.cast() = 'a', ""); + }; + + std::map map{{2, 'c'}}; + + test(map); + test(entt::forward_as_meta(map)); + test(entt::forward_as_meta(std::as_const(map))); +} + +TEST_F(MetaContainer, KeyOnlyAssociativeContainerConstMetaAny) { + auto test = [](const entt::meta_any any) { + auto view = any.as_associative_container(); + + ASSERT_TRUE(view); + ASSERT_EQ(view.value_type(), (entt::resolve())); + + ASSERT_EQ(view.find(2)->first.try_cast(), nullptr); + ASSERT_NE(view.find(2)->first.try_cast(), nullptr); + ASSERT_EQ(view.find(2)->first.cast(), 2); + ASSERT_EQ(view.find(2)->first.cast(), 2); + }; + + std::set set{2}; + + test(set); + test(entt::forward_as_meta(set)); + test(entt::forward_as_meta(std::as_const(set))); +} + +TEST_F(MetaContainer, StdVectorBool) { + using proxy_type = typename std::vector::reference; + using const_proxy_type = typename std::vector::const_reference; + + std::vector vec{}; + auto any = entt::forward_as_meta(vec); + auto cany = std::as_const(any).as_ref(); + + auto view = any.as_sequence_container(); + auto cview = cany.as_sequence_container(); + + ASSERT_TRUE(view); + ASSERT_EQ(view.value_type(), entt::resolve()); + + ASSERT_EQ(view.size(), 0u); + ASSERT_EQ(view.begin(), view.end()); + ASSERT_TRUE(view.resize(3u)); + ASSERT_EQ(view.size(), 3u); + ASSERT_NE(view.begin(), view.end()); + + view[0].cast() = true; + view[1].cast() = true; + view[2].cast() = false; + + ASSERT_EQ(cview[1u].cast(), true); + + auto it = view.begin(); + auto ret = view.insert(it, true); + + ASSERT_TRUE(ret); + ASSERT_FALSE(view.insert(ret, invalid_type{})); + ASSERT_TRUE(view.insert(++ret, false)); + + ASSERT_EQ(view.size(), 5u); + ASSERT_EQ(view.begin()->cast(), true); + ASSERT_EQ((++cview.begin())->cast(), false); + + it = view.begin(); + ret = view.erase(it); + + ASSERT_TRUE(ret); + ASSERT_EQ(view.size(), 4u); + ASSERT_EQ(ret->cast(), false); + + ASSERT_TRUE(view.clear()); + ASSERT_EQ(cview.size(), 0u); +} diff --git a/modules/entt/test/entt/meta/meta_conv.cpp b/modules/entt/test/entt/meta/meta_conv.cpp new file mode 100644 index 0000000..7801e68 --- /dev/null +++ b/modules/entt/test/entt/meta/meta_conv.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include +#include +#include + +struct clazz_t { + clazz_t() = default; + + operator int() const { + return value; + } + + bool to_bool() const { + return (value != 0); + } + + int value; +}; + +double conv_to_double(const clazz_t &instance) { + return instance.value * 2.; +} + +struct MetaConv: ::testing::Test { + void SetUp() override { + using namespace entt::literals; + + entt::meta() + .type("clazz"_hs) + .conv() + .conv<&clazz_t::to_bool>() + .conv(); + } + + void TearDown() override { + entt::meta_reset(); + } +}; + +TEST_F(MetaConv, Functionalities) { + auto any = entt::resolve().construct(); + any.cast().value = 42; + + const auto as_int = std::as_const(any).allow_cast(); + const auto as_bool = std::as_const(any).allow_cast(); + const auto as_double = std::as_const(any).allow_cast(); + + ASSERT_FALSE(any.allow_cast()); + + ASSERT_TRUE(as_int); + ASSERT_TRUE(as_bool); + ASSERT_TRUE(as_double); + + ASSERT_EQ(as_int.cast(), any.cast().operator int()); + ASSERT_EQ(as_bool.cast(), any.cast().to_bool()); + ASSERT_EQ(as_double.cast(), conv_to_double(any.cast())); +} + +TEST_F(MetaConv, ReRegistration) { + SetUp(); + + auto *node = entt::internal::meta_node::resolve(); + + ASSERT_NE(node->conv, nullptr); + ASSERT_NE(node->conv->next, nullptr); + ASSERT_NE(node->conv->next->next, nullptr); + ASSERT_EQ(node->conv->next->next->next, nullptr); +} diff --git a/modules/entt/test/entt/meta/meta_ctor.cpp b/modules/entt/test/entt/meta/meta_ctor.cpp new file mode 100644 index 0000000..c5a240d --- /dev/null +++ b/modules/entt/test/entt/meta/meta_ctor.cpp @@ -0,0 +1,213 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +struct base_t { + base_t() + : value{'c'} {} + + char value; +}; + +struct derived_t: base_t { + derived_t() + : base_t{} {} +}; + +struct clazz_t { + clazz_t(const base_t &other, int &iv) + : clazz_t{iv, other.value} {} + + clazz_t(const int &iv, char cv) + : i{iv}, c{cv} {} + + operator int() const { + return i; + } + + static clazz_t factory(int value) { + return {value, 'c'}; + } + + static clazz_t factory(base_t other, int value, int mul) { + return {value * mul, other.value}; + } + + int i{}; + char c{}; +}; + +double double_factory() { + return 42.; +} + +struct MetaCtor: ::testing::Test { + void SetUp() override { + using namespace entt::literals; + + entt::meta() + .type("double"_hs) + .ctor(); + + entt::meta() + .type("derived"_hs) + .base(); + + entt::meta() + .type("clazz"_hs) + .ctor<&entt::registry::emplace_or_replace, entt::as_ref_t>() + .ctor() + .ctor() + .ctor(clazz_t::factory)>() + .ctor(clazz_t::factory)>() + .conv(); + } + + void TearDown() override { + entt::meta_reset(); + } +}; + +TEST_F(MetaCtor, Functionalities) { + auto any = entt::resolve().construct(42, 'c'); + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast().i, 42); + ASSERT_EQ(any.cast().c, 'c'); +} + +TEST_F(MetaCtor, Func) { + auto any = entt::resolve().construct(42); + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast().i, 42); + ASSERT_EQ(any.cast().c, 'c'); +} + +TEST_F(MetaCtor, MetaAnyArgs) { + auto any = entt::resolve().construct(entt::meta_any{42}, entt::meta_any{'c'}); + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast().i, 42); + ASSERT_EQ(any.cast().c, 'c'); +} + +TEST_F(MetaCtor, InvalidArgs) { + ASSERT_FALSE(entt::resolve().construct(entt::meta_any{}, derived_t{})); +} + +TEST_F(MetaCtor, CastAndConvert) { + auto any = entt::resolve().construct(derived_t{}, clazz_t{42, 'd'}); + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast().i, 42); + ASSERT_EQ(any.cast().c, 'c'); +} + +TEST_F(MetaCtor, ArithmeticConversion) { + auto any = entt::resolve().construct(true, 4.2); + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast().i, 1); + ASSERT_EQ(any.cast().c, char{4}); +} + +TEST_F(MetaCtor, ConstNonConstRefArgs) { + int ivalue = 42; + const char cvalue = 'c'; + auto any = entt::resolve().construct(entt::forward_as_meta(ivalue), entt::forward_as_meta(cvalue)); + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast().i, 42); + ASSERT_EQ(any.cast().c, 'c'); +} + +TEST_F(MetaCtor, WrongConstness) { + int value = 42; + auto any = entt::resolve().construct(derived_t{}, entt::forward_as_meta(value)); + auto other = entt::resolve().construct(derived_t{}, entt::forward_as_meta(std::as_const(value))); + + ASSERT_TRUE(any); + ASSERT_FALSE(other); + ASSERT_EQ(any.cast().i, 42); + ASSERT_EQ(any.cast().c, 'c'); +} + +TEST_F(MetaCtor, FuncMetaAnyArgs) { + auto any = entt::resolve().construct(entt::meta_any{42}); + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast().i, 42); + ASSERT_EQ(any.cast().c, 'c'); +} + +TEST_F(MetaCtor, FuncCastAndConvert) { + auto any = entt::resolve().construct(derived_t{}, 3., clazz_t{3, 'd'}); + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast().i, 9); + ASSERT_EQ(any.cast().c, 'c'); +} + +TEST_F(MetaCtor, FuncArithmeticConversion) { + auto any = entt::resolve().construct(4.2); + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast().i, 4); + ASSERT_EQ(any.cast().c, 'c'); +} + +TEST_F(MetaCtor, FuncConstNonConstRefArgs) { + int ivalue = 42; + auto any = entt::resolve().construct(entt::forward_as_meta(ivalue)); + auto other = entt::resolve().construct(entt::make_meta(ivalue)); + + ASSERT_TRUE(any); + ASSERT_TRUE(other); + ASSERT_EQ(any.cast().i, 42); + ASSERT_EQ(other.cast().i, 42); +} + +TEST_F(MetaCtor, ExternalMemberFunction) { + entt::registry registry; + const auto entity = registry.create(); + + ASSERT_FALSE(registry.all_of(entity)); + + const auto any = entt::resolve().construct(entt::forward_as_meta(registry), entity, 3, 'c'); + + ASSERT_TRUE(any); + ASSERT_TRUE(registry.all_of(entity)); + ASSERT_EQ(registry.get(entity).i, 3); + ASSERT_EQ(registry.get(entity).c, 'c'); +} + +TEST_F(MetaCtor, OverrideImplicitlyGeneratedDefaultConstructor) { + auto type = entt::resolve(); + auto any = type.construct(); + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), 42.); +} + +TEST_F(MetaCtor, NonDefaultConstructibleType) { + auto type = entt::resolve(); + // no implicitly generated default constructor + ASSERT_FALSE(type.construct()); +} + +TEST_F(MetaCtor, ReRegistration) { + SetUp(); + + auto *node = entt::internal::meta_node::resolve(); + + ASSERT_NE(node->ctor, nullptr); + // implicitly generated default constructor is not cleared + ASSERT_NE(node->default_constructor, nullptr); +} diff --git a/modules/entt/test/entt/meta/meta_data.cpp b/modules/entt/test/entt/meta/meta_data.cpp new file mode 100644 index 0000000..8b0d0d5 --- /dev/null +++ b/modules/entt/test/entt/meta/meta_data.cpp @@ -0,0 +1,692 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct base_t { + virtual ~base_t() = default; + + static void destroy(base_t &) { + ++counter; + } + + inline static int counter = 0; + int value{3}; +}; + +struct derived_t: base_t { + derived_t() {} +}; + +struct clazz_t { + clazz_t() + : i{0}, + j{1}, + base{} {} + + operator int() const { + return h; + } + + int i{0}; + const int j{1}; + base_t base{}; + inline static int h{2}; + inline static const int k{3}; +}; + +struct setter_getter_t { + setter_getter_t() + : value{0} {} + + int setter(double val) { + return value = static_cast(val); + } + + int getter() { + return value; + } + + int setter_with_ref(const int &val) { + return value = val; + } + + const int &getter_with_ref() { + return value; + } + + static int static_setter(setter_getter_t &type, int value) { + return type.value = value; + } + + static int static_getter(const setter_getter_t &type) { + return type.value; + } + + int value; +}; + +struct multi_setter_t { + multi_setter_t() + : value{0} {} + + void from_double(double val) { + value = val; + } + + void from_string(const char *val) { + value = std::atoi(val); + } + + int value; +}; + +struct array_t { + static inline int global[3]; + int local[5]; +}; + +enum class property_t { + random, + value +}; + +struct MetaData: ::testing::Test { + void SetUp() override { + using namespace entt::literals; + + entt::meta() + .type("double"_hs); + + entt::meta() + .type("base"_hs) + .dtor() + .data<&base_t::value>("value"_hs); + + entt::meta() + .type("derived"_hs) + .base() + .dtor() + .data<&base_t::value>("value_from_base"_hs); + + entt::meta() + .type("clazz"_hs) + .data<&clazz_t::i, entt::as_ref_t>("i"_hs) + .prop(3, 0) + .data<&clazz_t::i, entt::as_cref_t>("ci"_hs) + .data<&clazz_t::j>("j"_hs) + .prop(true, 1) + .data<&clazz_t::h>("h"_hs) + .prop(property_t::random, 2) + .data<&clazz_t::k>("k"_hs) + .prop(property_t::value, 3) + .data<&clazz_t::base>("base"_hs) + .data<&clazz_t::i, entt::as_void_t>("void"_hs) + .conv(); + + entt::meta() + .type("setter_getter"_hs) + .data<&setter_getter_t::static_setter, &setter_getter_t::static_getter>("x"_hs) + .data<&setter_getter_t::setter, &setter_getter_t::getter>("y"_hs) + .data<&setter_getter_t::static_setter, &setter_getter_t::getter>("z"_hs) + .data<&setter_getter_t::setter_with_ref, &setter_getter_t::getter_with_ref>("w"_hs) + .data("z_ro"_hs) + .data("value"_hs); + + entt::meta() + .type("multi_setter"_hs) + .data, &multi_setter_t::value>("value"_hs); + + entt::meta() + .type("array"_hs) + .data<&array_t::global>("global"_hs) + .data<&array_t::local>("local"_hs); + + base_t::counter = 0; + } + + void TearDown() override { + entt::meta_reset(); + } +}; + +using MetaDataDeathTest = MetaData; + +TEST_F(MetaData, Functionalities) { + using namespace entt::literals; + + auto data = entt::resolve().data("i"_hs); + clazz_t instance{}; + + ASSERT_TRUE(data); + ASSERT_EQ(data.arity(), 1u); + ASSERT_EQ(data.type(), entt::resolve()); + ASSERT_EQ(data.arg(0u), entt::resolve()); + ASSERT_EQ(data.id(), "i"_hs); + ASSERT_FALSE(data.is_const()); + ASSERT_FALSE(data.is_static()); + ASSERT_EQ(data.get(instance).cast(), 0); + ASSERT_TRUE(data.set(instance, 42)); + ASSERT_EQ(data.get(instance).cast(), 42); + + for(auto curr: data.prop()) { + ASSERT_EQ(curr.key(), 3); + ASSERT_EQ(curr.value(), 0); + } + + ASSERT_FALSE(data.prop(2)); + ASSERT_FALSE(data.prop('c')); + + auto prop = data.prop(3); + + ASSERT_TRUE(prop); + ASSERT_EQ(prop.key(), 3); + ASSERT_EQ(prop.value(), 0); +} + +TEST_F(MetaData, Const) { + using namespace entt::literals; + + auto data = entt::resolve().data("j"_hs); + clazz_t instance{}; + + ASSERT_TRUE(data); + ASSERT_EQ(data.arity(), 1u); + ASSERT_EQ(data.type(), entt::resolve()); + ASSERT_EQ(data.arg(0u), entt::resolve()); + ASSERT_EQ(data.id(), "j"_hs); + ASSERT_TRUE(data.is_const()); + ASSERT_FALSE(data.is_static()); + ASSERT_EQ(data.get(instance).cast(), 1); + ASSERT_FALSE(data.set(instance, 42)); + ASSERT_EQ(data.get(instance).cast(), 1); + + for(auto curr: data.prop()) { + ASSERT_EQ(curr.key(), true); + ASSERT_EQ(curr.value(), 1); + } + + ASSERT_FALSE(data.prop(false)); + ASSERT_FALSE(data.prop('c')); + + auto prop = data.prop(true); + + ASSERT_TRUE(prop); + ASSERT_EQ(prop.key(), true); + ASSERT_EQ(prop.value(), 1); +} + +TEST_F(MetaData, Static) { + using namespace entt::literals; + + auto data = entt::resolve().data("h"_hs); + + ASSERT_TRUE(data); + ASSERT_EQ(data.arity(), 1u); + ASSERT_EQ(data.type(), entt::resolve()); + ASSERT_EQ(data.arg(0u), entt::resolve()); + ASSERT_EQ(data.id(), "h"_hs); + ASSERT_FALSE(data.is_const()); + ASSERT_TRUE(data.is_static()); + ASSERT_EQ(data.get({}).cast(), 2); + ASSERT_TRUE(data.set({}, 42)); + ASSERT_EQ(data.get({}).cast(), 42); + + for(auto curr: data.prop()) { + ASSERT_EQ(curr.key(), property_t::random); + ASSERT_EQ(curr.value(), 2); + } + + ASSERT_FALSE(data.prop(property_t::value)); + ASSERT_FALSE(data.prop('c')); + + auto prop = data.prop(property_t::random); + + ASSERT_TRUE(prop); + ASSERT_EQ(prop.key(), property_t::random); + ASSERT_EQ(prop.value(), 2); +} + +TEST_F(MetaData, ConstStatic) { + using namespace entt::literals; + + auto data = entt::resolve().data("k"_hs); + + ASSERT_TRUE(data); + ASSERT_EQ(data.arity(), 1u); + ASSERT_EQ(data.type(), entt::resolve()); + ASSERT_EQ(data.arg(0u), entt::resolve()); + ASSERT_EQ(data.id(), "k"_hs); + ASSERT_TRUE(data.is_const()); + ASSERT_TRUE(data.is_static()); + ASSERT_EQ(data.get({}).cast(), 3); + ASSERT_FALSE(data.set({}, 42)); + ASSERT_EQ(data.get({}).cast(), 3); + + for(auto curr: data.prop()) { + ASSERT_EQ(curr.key(), property_t::value); + ASSERT_EQ(curr.value(), 3); + } + + ASSERT_FALSE(data.prop(property_t::random)); + ASSERT_FALSE(data.prop('c')); + + auto prop = data.prop(property_t::value); + + ASSERT_TRUE(prop); + ASSERT_EQ(prop.key(), property_t::value); + ASSERT_EQ(prop.value(), 3); +} + +TEST_F(MetaData, GetMetaAnyArg) { + using namespace entt::literals; + + entt::meta_any any{clazz_t{}}; + any.cast().i = 99; + const auto value = entt::resolve().data("i"_hs).get(any); + + ASSERT_TRUE(value); + ASSERT_TRUE(static_cast(value.cast())); + ASSERT_EQ(value.cast(), 99); +} + +TEST_F(MetaData, GetInvalidArg) { + using namespace entt::literals; + + auto instance = 0; + ASSERT_FALSE(entt::resolve().data("i"_hs).get(instance)); +} + +TEST_F(MetaData, SetMetaAnyArg) { + using namespace entt::literals; + + entt::meta_any any{clazz_t{}}; + entt::meta_any value{42}; + + ASSERT_EQ(any.cast().i, 0); + ASSERT_TRUE(entt::resolve().data("i"_hs).set(any, value)); + ASSERT_EQ(any.cast().i, 42); +} + +TEST_F(MetaData, SetInvalidArg) { + using namespace entt::literals; + + ASSERT_FALSE(entt::resolve().data("i"_hs).set({}, 'c')); +} + +TEST_F(MetaData, SetCast) { + using namespace entt::literals; + + clazz_t instance{}; + + ASSERT_EQ(base_t::counter, 0); + ASSERT_TRUE(entt::resolve().data("base"_hs).set(instance, derived_t{})); + ASSERT_EQ(base_t::counter, 1); +} + +TEST_F(MetaData, SetConvert) { + using namespace entt::literals; + + clazz_t instance{}; + instance.h = 42; + + ASSERT_EQ(instance.i, 0); + ASSERT_TRUE(entt::resolve().data("i"_hs).set(instance, instance)); + ASSERT_EQ(instance.i, 42); +} + +TEST_F(MetaData, SetByRef) { + using namespace entt::literals; + + entt::meta_any any{clazz_t{}}; + int value{42}; + + ASSERT_EQ(any.cast().i, 0); + ASSERT_TRUE(entt::resolve().data("i"_hs).set(any, entt::make_meta(value))); + ASSERT_EQ(any.cast().i, 42); + + value = 3; + auto wrapper = entt::make_meta(value); + + ASSERT_TRUE(entt::resolve().data("i"_hs).set(any, wrapper.as_ref())); + ASSERT_EQ(any.cast().i, 3); +} + +TEST_F(MetaData, SetByConstRef) { + using namespace entt::literals; + + entt::meta_any any{clazz_t{}}; + int value{42}; + + ASSERT_EQ(any.cast().i, 0); + ASSERT_TRUE(entt::resolve().data("i"_hs).set(any, entt::make_meta(value))); + ASSERT_EQ(any.cast().i, 42); + + value = 3; + auto wrapper = entt::make_meta(value); + + ASSERT_TRUE(entt::resolve().data("i"_hs).set(any, wrapper.as_ref())); + ASSERT_EQ(any.cast().i, 3); +} + +TEST_F(MetaData, SetterGetterAsFreeFunctions) { + using namespace entt::literals; + + auto data = entt::resolve().data("x"_hs); + setter_getter_t instance{}; + + ASSERT_TRUE(data); + ASSERT_EQ(data.arity(), 1u); + ASSERT_EQ(data.type(), entt::resolve()); + ASSERT_EQ(data.arg(0u), entt::resolve()); + ASSERT_EQ(data.id(), "x"_hs); + ASSERT_FALSE(data.is_const()); + ASSERT_FALSE(data.is_static()); + ASSERT_EQ(data.get(instance).cast(), 0); + ASSERT_TRUE(data.set(instance, 42)); + ASSERT_EQ(data.get(instance).cast(), 42); +} + +TEST_F(MetaData, SetterGetterAsMemberFunctions) { + using namespace entt::literals; + + auto data = entt::resolve().data("y"_hs); + setter_getter_t instance{}; + + ASSERT_TRUE(data); + ASSERT_EQ(data.arity(), 1u); + ASSERT_EQ(data.type(), entt::resolve()); + ASSERT_EQ(data.arg(0u), entt::resolve()); + ASSERT_EQ(data.id(), "y"_hs); + ASSERT_FALSE(data.is_const()); + ASSERT_FALSE(data.is_static()); + ASSERT_EQ(data.get(instance).cast(), 0); + ASSERT_TRUE(data.set(instance, 42.)); + ASSERT_EQ(data.get(instance).cast(), 42); + ASSERT_TRUE(data.set(instance, 3)); + ASSERT_EQ(data.get(instance).cast(), 3); +} + +TEST_F(MetaData, SetterGetterWithRefAsMemberFunctions) { + using namespace entt::literals; + + auto data = entt::resolve().data("w"_hs); + setter_getter_t instance{}; + + ASSERT_TRUE(data); + ASSERT_EQ(data.arity(), 1u); + ASSERT_EQ(data.type(), entt::resolve()); + ASSERT_EQ(data.arg(0u), entt::resolve()); + ASSERT_EQ(data.id(), "w"_hs); + ASSERT_FALSE(data.is_const()); + ASSERT_FALSE(data.is_static()); + ASSERT_EQ(data.get(instance).cast(), 0); + ASSERT_TRUE(data.set(instance, 42)); + ASSERT_EQ(data.get(instance).cast(), 42); +} + +TEST_F(MetaData, SetterGetterMixed) { + using namespace entt::literals; + + auto data = entt::resolve().data("z"_hs); + setter_getter_t instance{}; + + ASSERT_TRUE(data); + ASSERT_EQ(data.arity(), 1u); + ASSERT_EQ(data.type(), entt::resolve()); + ASSERT_EQ(data.arg(0u), entt::resolve()); + ASSERT_EQ(data.id(), "z"_hs); + ASSERT_FALSE(data.is_const()); + ASSERT_FALSE(data.is_static()); + ASSERT_EQ(data.get(instance).cast(), 0); + ASSERT_TRUE(data.set(instance, 42)); + ASSERT_EQ(data.get(instance).cast(), 42); +} + +TEST_F(MetaData, SetterGetterReadOnly) { + using namespace entt::literals; + + auto data = entt::resolve().data("z_ro"_hs); + setter_getter_t instance{}; + + ASSERT_TRUE(data); + ASSERT_EQ(data.arity(), 0u); + ASSERT_EQ(data.type(), entt::resolve()); + ASSERT_EQ(data.arg(0u), entt::meta_type{}); + ASSERT_EQ(data.id(), "z_ro"_hs); + ASSERT_TRUE(data.is_const()); + ASSERT_FALSE(data.is_static()); + ASSERT_EQ(data.get(instance).cast(), 0); + ASSERT_FALSE(data.set(instance, 42)); + ASSERT_EQ(data.get(instance).cast(), 0); +} + +TEST_F(MetaData, SetterGetterReadOnlyDataMember) { + using namespace entt::literals; + + auto data = entt::resolve().data("value"_hs); + setter_getter_t instance{}; + + ASSERT_TRUE(data); + ASSERT_EQ(data.arity(), 0u); + ASSERT_EQ(data.type(), entt::resolve()); + ASSERT_EQ(data.arg(0u), entt::meta_type{}); + ASSERT_EQ(data.id(), "value"_hs); + ASSERT_TRUE(data.is_const()); + ASSERT_FALSE(data.is_static()); + ASSERT_EQ(data.get(instance).cast(), 0); + ASSERT_FALSE(data.set(instance, 42)); + ASSERT_EQ(data.get(instance).cast(), 0); +} + +TEST_F(MetaData, MultiSetter) { + using namespace entt::literals; + + auto data = entt::resolve().data("value"_hs); + multi_setter_t instance{}; + + ASSERT_TRUE(data); + ASSERT_EQ(data.arity(), 2u); + ASSERT_EQ(data.type(), entt::resolve()); + ASSERT_EQ(data.arg(0u), entt::resolve()); + ASSERT_EQ(data.arg(1u), entt::resolve()); + ASSERT_EQ(data.arg(2u), entt::meta_type{}); + ASSERT_EQ(data.id(), "value"_hs); + ASSERT_FALSE(data.is_const()); + ASSERT_FALSE(data.is_static()); + ASSERT_EQ(data.get(instance).cast(), 0); + ASSERT_TRUE(data.set(instance, 42)); + ASSERT_EQ(data.get(instance).cast(), 42); + ASSERT_TRUE(data.set(instance, 3.)); + ASSERT_EQ(data.get(instance).cast(), 3); + ASSERT_FALSE(data.set(instance, std::string{"99"})); + ASSERT_TRUE(data.set(instance, std::string{"99"}.c_str())); + ASSERT_EQ(data.get(instance).cast(), 99); +} + +TEST_F(MetaData, ConstInstance) { + using namespace entt::literals; + + clazz_t instance{}; + + ASSERT_NE(entt::resolve().data("i"_hs).get(instance).try_cast(), nullptr); + ASSERT_NE(entt::resolve().data("i"_hs).get(instance).try_cast(), nullptr); + ASSERT_EQ(entt::resolve().data("i"_hs).get(std::as_const(instance)).try_cast(), nullptr); + // as_ref_t adapts to the constness of the passed object and returns const references in case + ASSERT_NE(entt::resolve().data("i"_hs).get(std::as_const(instance)).try_cast(), nullptr); + + ASSERT_TRUE(entt::resolve().data("i"_hs).get(instance)); + ASSERT_TRUE(entt::resolve().data("i"_hs).set(instance, 3)); + ASSERT_TRUE(entt::resolve().data("i"_hs).get(std::as_const(instance))); + ASSERT_FALSE(entt::resolve().data("i"_hs).set(std::as_const(instance), 3)); + + ASSERT_TRUE(entt::resolve().data("ci"_hs).get(instance)); + ASSERT_TRUE(entt::resolve().data("ci"_hs).set(instance, 3)); + ASSERT_TRUE(entt::resolve().data("ci"_hs).get(std::as_const(instance))); + ASSERT_FALSE(entt::resolve().data("ci"_hs).set(std::as_const(instance), 3)); + + ASSERT_TRUE(entt::resolve().data("j"_hs).get(instance)); + ASSERT_FALSE(entt::resolve().data("j"_hs).set(instance, 3)); + ASSERT_TRUE(entt::resolve().data("j"_hs).get(std::as_const(instance))); + ASSERT_FALSE(entt::resolve().data("j"_hs).set(std::as_const(instance), 3)); +} + +TEST_F(MetaData, ArrayStatic) { + using namespace entt::literals; + + auto data = entt::resolve().data("global"_hs); + + ASSERT_TRUE(data); + ASSERT_EQ(data.arity(), 1u); + ASSERT_EQ(data.type(), entt::resolve()); + ASSERT_EQ(data.arg(0u), entt::resolve()); + ASSERT_EQ(data.id(), "global"_hs); + ASSERT_FALSE(data.is_const()); + ASSERT_TRUE(data.is_static()); + ASSERT_TRUE(data.type().is_array()); + ASSERT_FALSE(data.get({})); +} + +TEST_F(MetaData, Array) { + using namespace entt::literals; + + auto data = entt::resolve().data("local"_hs); + array_t instance{}; + + ASSERT_TRUE(data); + ASSERT_EQ(data.arity(), 1u); + ASSERT_EQ(data.type(), entt::resolve()); + ASSERT_EQ(data.arg(0u), entt::resolve()); + ASSERT_EQ(data.id(), "local"_hs); + ASSERT_FALSE(data.is_const()); + ASSERT_FALSE(data.is_static()); + ASSERT_TRUE(data.type().is_array()); + ASSERT_FALSE(data.get(instance)); +} + +TEST_F(MetaData, AsVoid) { + using namespace entt::literals; + + auto data = entt::resolve().data("void"_hs); + clazz_t instance{}; + + ASSERT_TRUE(data); + ASSERT_EQ(data.arity(), 1u); + ASSERT_EQ(data.type(), entt::resolve()); + ASSERT_EQ(data.arg(0u), entt::resolve()); + ASSERT_TRUE(data.set(instance, 42)); + ASSERT_EQ(instance.i, 42); + ASSERT_EQ(data.get(instance), entt::meta_any{std::in_place_type}); +} + +TEST_F(MetaData, AsRef) { + using namespace entt::literals; + + clazz_t instance{}; + auto data = entt::resolve().data("i"_hs); + + ASSERT_TRUE(data); + ASSERT_EQ(data.arity(), 1u); + ASSERT_EQ(data.type(), entt::resolve()); + ASSERT_EQ(data.arg(0u), entt::resolve()); + ASSERT_EQ(instance.i, 0); + + data.get(instance).cast() = 3; + + ASSERT_EQ(instance.i, 3); +} + +TEST_F(MetaData, AsConstRef) { + using namespace entt::literals; + + clazz_t instance{}; + auto data = entt::resolve().data("ci"_hs); + + ASSERT_EQ(instance.i, 0); + ASSERT_EQ(data.arity(), 1u); + ASSERT_EQ(data.type(), entt::resolve()); + ASSERT_EQ(data.arg(0u), entt::resolve()); + ASSERT_EQ(data.get(instance).cast(), 0); + ASSERT_EQ(data.get(instance).cast(), 0); + ASSERT_EQ(instance.i, 0); +} + +TEST_F(MetaDataDeathTest, AsConstRef) { + using namespace entt::literals; + + clazz_t instance{}; + auto data = entt::resolve().data("ci"_hs); + + ASSERT_DEATH(data.get(instance).cast() = 3, ""); +} + +TEST_F(MetaData, SetGetBaseData) { + using namespace entt::literals; + + auto type = entt::resolve(); + derived_t instance{}; + + ASSERT_TRUE(type.data("value"_hs)); + + ASSERT_EQ(instance.value, 3); + ASSERT_TRUE(type.data("value"_hs).set(instance, 42)); + ASSERT_EQ(type.data("value"_hs).get(instance).cast(), 42); + ASSERT_EQ(instance.value, 42); +} + +TEST_F(MetaData, SetGetFromBase) { + using namespace entt::literals; + + auto type = entt::resolve(); + derived_t instance{}; + + ASSERT_TRUE(type.data("value_from_base"_hs)); + + ASSERT_EQ(instance.value, 3); + ASSERT_TRUE(type.data("value_from_base"_hs).set(instance, 42)); + ASSERT_EQ(type.data("value_from_base"_hs).get(instance).cast(), 42); + ASSERT_EQ(instance.value, 42); +} + +TEST_F(MetaData, ReRegistration) { + using namespace entt::literals; + + SetUp(); + + auto *node = entt::internal::meta_node::resolve(); + auto type = entt::resolve(); + + ASSERT_NE(node->data, nullptr); + ASSERT_EQ(node->data->next, nullptr); + ASSERT_TRUE(type.data("value"_hs)); + + entt::meta().data<&base_t::value>("field"_hs); + + ASSERT_NE(node->data, nullptr); + ASSERT_EQ(node->data->next, nullptr); + ASSERT_FALSE(type.data("value"_hs)); + ASSERT_TRUE(type.data("field"_hs)); +} + +TEST_F(MetaData, NameCollision) { + using namespace entt::literals; + + ASSERT_NO_FATAL_FAILURE(entt::meta().data<&clazz_t::j>("j"_hs)); + ASSERT_TRUE(entt::resolve().data("j"_hs)); + + ASSERT_NO_FATAL_FAILURE(entt::meta().data<&clazz_t::j>("cj"_hs)); + ASSERT_FALSE(entt::resolve().data("j"_hs)); + ASSERT_TRUE(entt::resolve().data("cj"_hs)); +} + +TEST_F(MetaDataDeathTest, NameCollision) { + using namespace entt::literals; + + ASSERT_DEATH(entt::meta().data<&clazz_t::j>("i"_hs), ""); +} diff --git a/modules/entt/test/entt/meta/meta_dtor.cpp b/modules/entt/test/entt/meta/meta_dtor.cpp new file mode 100644 index 0000000..60aaf51 --- /dev/null +++ b/modules/entt/test/entt/meta/meta_dtor.cpp @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include +#include +#include + +struct clazz_t { + clazz_t() { + ++counter; + } + + static void destroy_decr(clazz_t &) { + --counter; + } + + void destroy_incr() const { + ++counter; + } + + inline static int counter = 0; +}; + +struct MetaDtor: ::testing::Test { + void SetUp() override { + using namespace entt::literals; + + entt::meta() + .type("clazz"_hs) + .dtor(); + + clazz_t::counter = 0; + } + + void TearDown() override { + entt::meta_reset(); + } +}; + +TEST_F(MetaDtor, Functionalities) { + ASSERT_EQ(clazz_t::counter, 0); + + auto any = entt::resolve().construct(); + auto cref = std::as_const(any).as_ref(); + auto ref = any.as_ref(); + + ASSERT_TRUE(any); + ASSERT_TRUE(cref); + ASSERT_TRUE(ref); + + ASSERT_EQ(clazz_t::counter, 1); + + cref.reset(); + ref.reset(); + + ASSERT_TRUE(any); + ASSERT_FALSE(cref); + ASSERT_FALSE(ref); + + ASSERT_EQ(clazz_t::counter, 1); + + any.reset(); + + ASSERT_FALSE(any); + ASSERT_FALSE(cref); + ASSERT_FALSE(ref); + + ASSERT_EQ(clazz_t::counter, 0); +} + +TEST_F(MetaDtor, AsRefConstruction) { + ASSERT_EQ(clazz_t::counter, 0); + + clazz_t instance{}; + auto any = entt::forward_as_meta(instance); + auto cany = entt::make_meta(instance); + auto cref = cany.as_ref(); + auto ref = any.as_ref(); + + ASSERT_TRUE(any); + ASSERT_TRUE(cany); + ASSERT_TRUE(cref); + ASSERT_TRUE(ref); + + ASSERT_EQ(clazz_t::counter, 1); + + any.reset(); + cany.reset(); + cref.reset(); + ref.reset(); + + ASSERT_FALSE(any); + ASSERT_FALSE(cany); + ASSERT_FALSE(cref); + ASSERT_FALSE(ref); + + ASSERT_EQ(clazz_t::counter, 1); +} + +TEST_F(MetaDtor, ReRegistration) { + SetUp(); + + auto *node = entt::internal::meta_node::resolve(); + + ASSERT_NE(node->dtor, nullptr); + + entt::meta().dtor<&clazz_t::destroy_incr>(); + entt::resolve().construct().reset(); + + ASSERT_EQ(clazz_t::counter, 2); +} diff --git a/modules/entt/test/entt/meta/meta_func.cpp b/modules/entt/test/entt/meta/meta_func.cpp new file mode 100644 index 0000000..f478a74 --- /dev/null +++ b/modules/entt/test/entt/meta/meta_func.cpp @@ -0,0 +1,613 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct base_t { + base_t() {} + virtual ~base_t() = default; + + static void destroy(base_t &) { + ++counter; + } + + void setter(int v) { + value = v; + } + + int getter() const { + return value; + } + + static void static_setter(base_t &ref, int v) { + ref.value = v; + } + + inline static int counter = 0; + int value{3}; +}; + +void fake_member(base_t &instance, int value) { + instance.value = value; +} + +int fake_const_member(const base_t &instance) { + return instance.value; +} + +struct derived_t: base_t { + derived_t() + : base_t{} {} +}; + +struct func_t { + int f(const base_t &, int a, int b) { + return f(a, b); + } + + int f(int a, int b) { + value = a; + return b * b; + } + + int f(int v) const { + return v * v; + } + + void g(int v) { + value = v * v; + } + + static int h(int &v) { + return (v *= value); + } + + static void k(int v) { + value = v; + } + + int v(int v) const { + return (value = v); + } + + int &a() const { + return value; + } + + operator int() const { + return value; + } + + inline static int value = 0; +}; + +struct MetaFunc: ::testing::Test { + void SetUp() override { + using namespace entt::literals; + + entt::meta() + .type("double"_hs); + + entt::meta() + .type("base"_hs) + .dtor() + .func<&base_t::setter>("setter"_hs) + .func("fake_member"_hs) + .func("fake_const_member"_hs); + + entt::meta() + .type("derived"_hs) + .base() + .func<&base_t::setter>("setter_from_base"_hs) + .func<&base_t::getter>("getter_from_base"_hs) + .func<&base_t::static_setter>("static_setter_from_base"_hs) + .dtor(); + + entt::meta() + .type("func"_hs) + .func<&entt::registry::emplace_or_replace>("emplace"_hs) + .func(&func_t::f)>("f3"_hs) + .func(&func_t::f)>("f2"_hs) + .prop(true, false) + .func(&func_t::f)>("f1"_hs) + .prop(true, false) + .func<&func_t::g>("g"_hs) + .prop(true, false) + .func("h"_hs) + .prop(true, false) + .func("k"_hs) + .prop(true, false) + .func<&func_t::v, entt::as_void_t>("v"_hs) + .func<&func_t::a, entt::as_ref_t>("a"_hs) + .func<&func_t::a, entt::as_cref_t>("ca"_hs) + .conv(); + + base_t::counter = 0; + } + + void TearDown() override { + entt::meta_reset(); + } + + std::size_t reset_and_check() { + std::size_t count = 0; + + for(auto func: entt::resolve().func()) { + count += static_cast(func); + } + + SetUp(); + + for(auto func: entt::resolve().func()) { + count -= static_cast(func); + } + + return count; + }; +}; + +using MetaFuncDeathTest = MetaFunc; + +TEST_F(MetaFunc, Functionalities) { + using namespace entt::literals; + + auto func = entt::resolve().func("f2"_hs); + func_t instance{}; + + ASSERT_TRUE(func); + ASSERT_EQ(func.id(), "f2"_hs); + ASSERT_EQ(func.arity(), 2u); + ASSERT_FALSE(func.is_const()); + ASSERT_FALSE(func.is_static()); + ASSERT_EQ(func.ret(), entt::resolve()); + ASSERT_EQ(func.arg(0u), entt::resolve()); + ASSERT_EQ(func.arg(1u), entt::resolve()); + ASSERT_FALSE(func.arg(2u)); + + auto any = func.invoke(instance, 3, 2); + auto empty = func.invoke(instance); + + ASSERT_FALSE(empty); + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), 4); + ASSERT_EQ(func_t::value, 3); + + for(auto curr: func.prop()) { + ASSERT_EQ(curr.key(), true); + ASSERT_FALSE(curr.value().template cast()); + } + + ASSERT_FALSE(func.prop(false)); + ASSERT_FALSE(func.prop('c')); + + auto prop = func.prop(true); + + ASSERT_TRUE(prop); + ASSERT_EQ(prop.key(), true); + ASSERT_FALSE(prop.value().cast()); +} + +TEST_F(MetaFunc, Const) { + using namespace entt::literals; + + auto func = entt::resolve().func("f1"_hs); + func_t instance{}; + + ASSERT_TRUE(func); + ASSERT_EQ(func.id(), "f1"_hs); + ASSERT_EQ(func.arity(), 1u); + ASSERT_TRUE(func.is_const()); + ASSERT_FALSE(func.is_static()); + ASSERT_EQ(func.ret(), entt::resolve()); + ASSERT_EQ(func.arg(0u), entt::resolve()); + ASSERT_FALSE(func.arg(1u)); + + auto any = func.invoke(instance, 4); + auto empty = func.invoke(instance, derived_t{}); + + ASSERT_FALSE(empty); + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), 16); + + for(auto curr: func.prop()) { + ASSERT_EQ(curr.key(), true); + ASSERT_FALSE(curr.value().template cast()); + } + + ASSERT_FALSE(func.prop(false)); + ASSERT_FALSE(func.prop('c')); + + auto prop = func.prop(true); + + ASSERT_TRUE(prop); + ASSERT_EQ(prop.key(), true); + ASSERT_FALSE(prop.value().cast()); +} + +TEST_F(MetaFunc, RetVoid) { + using namespace entt::literals; + + auto func = entt::resolve().func("g"_hs); + func_t instance{}; + + ASSERT_TRUE(func); + ASSERT_EQ(func.id(), "g"_hs); + ASSERT_EQ(func.arity(), 1u); + ASSERT_FALSE(func.is_const()); + ASSERT_FALSE(func.is_static()); + ASSERT_EQ(func.ret(), entt::resolve()); + ASSERT_EQ(func.arg(0u), entt::resolve()); + ASSERT_FALSE(func.arg(1u)); + + auto any = func.invoke(instance, 5); + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(func_t::value, 25); + + for(auto curr: func.prop()) { + ASSERT_EQ(curr.key(), true); + ASSERT_FALSE(curr.value().template cast()); + } + + ASSERT_FALSE(func.prop(false)); + ASSERT_FALSE(func.prop('c')); + + auto prop = func.prop(true); + + ASSERT_TRUE(prop); + ASSERT_EQ(prop.key(), true); + ASSERT_FALSE(prop.value().cast()); +} + +TEST_F(MetaFunc, Static) { + using namespace entt::literals; + + auto func = entt::resolve().func("h"_hs); + func_t::value = 2; + + ASSERT_TRUE(func); + ASSERT_EQ(func.id(), "h"_hs); + ASSERT_EQ(func.arity(), 1u); + ASSERT_FALSE(func.is_const()); + ASSERT_TRUE(func.is_static()); + ASSERT_EQ(func.ret(), entt::resolve()); + ASSERT_EQ(func.arg(0u), entt::resolve()); + ASSERT_FALSE(func.arg(1u)); + + auto any = func.invoke({}, 3); + auto empty = func.invoke({}, derived_t{}); + + ASSERT_FALSE(empty); + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), 6); + + for(auto curr: func.prop()) { + ASSERT_EQ(curr.key(), true); + ASSERT_FALSE(curr.value().template cast()); + } + + ASSERT_FALSE(func.prop(false)); + ASSERT_FALSE(func.prop('c')); + + auto prop = func.prop(true); + + ASSERT_TRUE(prop); + ASSERT_EQ(prop.key(), true); + ASSERT_FALSE(prop.value().cast()); +} + +TEST_F(MetaFunc, StaticRetVoid) { + using namespace entt::literals; + + auto func = entt::resolve().func("k"_hs); + + ASSERT_TRUE(func); + ASSERT_EQ(func.id(), "k"_hs); + ASSERT_EQ(func.arity(), 1u); + ASSERT_FALSE(func.is_const()); + ASSERT_TRUE(func.is_static()); + ASSERT_EQ(func.ret(), entt::resolve()); + ASSERT_EQ(func.arg(0u), entt::resolve()); + ASSERT_FALSE(func.arg(1u)); + + auto any = func.invoke({}, 42); + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(func_t::value, 42); + + for(auto curr: func.prop()) { + ASSERT_TRUE(curr); + ASSERT_EQ(curr.key(), true); + ASSERT_FALSE(curr.value().template cast()); + } + + ASSERT_FALSE(func.prop(false)); + ASSERT_FALSE(func.prop('c')); + + auto prop = func.prop(true); + + ASSERT_TRUE(prop); + ASSERT_EQ(prop.key(), true); + ASSERT_FALSE(prop.value().cast()); +} + +TEST_F(MetaFunc, StaticAsMember) { + using namespace entt::literals; + + base_t instance{}; + auto func = entt::resolve().func("fake_member"_hs); + auto any = func.invoke(instance, 42); + + ASSERT_TRUE(func); + ASSERT_EQ(func.id(), "fake_member"_hs); + ASSERT_EQ(func.arity(), 1u); + ASSERT_FALSE(func.is_const()); + ASSERT_FALSE(func.is_static()); + ASSERT_EQ(func.ret(), entt::resolve()); + ASSERT_EQ(func.arg(0u), entt::resolve()); + ASSERT_FALSE(func.arg(1u)); + + ASSERT_FALSE(func.invoke({}, 42)); + ASSERT_FALSE(func.invoke(std::as_const(instance), 42)); + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(instance.value, 42); +} + +TEST_F(MetaFunc, StaticAsConstMember) { + using namespace entt::literals; + + base_t instance{}; + auto func = entt::resolve().func("fake_const_member"_hs); + auto any = func.invoke(std::as_const(instance)); + + ASSERT_TRUE(func); + ASSERT_EQ(func.id(), "fake_const_member"_hs); + ASSERT_EQ(func.arity(), 0u); + ASSERT_TRUE(func.is_const()); + ASSERT_FALSE(func.is_static()); + ASSERT_EQ(func.ret(), entt::resolve()); + ASSERT_FALSE(func.arg(0u)); + + ASSERT_FALSE(func.invoke({})); + ASSERT_TRUE(func.invoke(instance)); + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), 3); +} + +TEST_F(MetaFunc, MetaAnyArgs) { + using namespace entt::literals; + + func_t instance; + auto any = entt::resolve().func("f1"_hs).invoke(instance, 3); + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), 9); +} + +TEST_F(MetaFunc, InvalidArgs) { + using namespace entt::literals; + + int value = 3; + ASSERT_FALSE(entt::resolve().func("f1"_hs).invoke(value, 'c')); +} + +TEST_F(MetaFunc, CastAndConvert) { + using namespace entt::literals; + + func_t instance; + instance.value = 3; + auto any = entt::resolve().func("f3"_hs).invoke(instance, derived_t{}, 0, instance); + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), 9); + ASSERT_EQ(instance.value, 0); +} + +TEST_F(MetaFunc, ArithmeticConversion) { + using namespace entt::literals; + + func_t instance; + auto any = entt::resolve().func("f2"_hs).invoke(instance, true, 4.2); + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); + ASSERT_EQ(any.cast(), 16); + ASSERT_EQ(instance.value, 1); +} + +TEST_F(MetaFunc, ArgsByRef) { + using namespace entt::literals; + + auto func = entt::resolve().func("h"_hs); + func_t::value = 2; + entt::meta_any any{3}; + int value = 4; + + ASSERT_EQ(func.invoke({}, entt::forward_as_meta(value)).cast(), 8); + ASSERT_EQ(func.invoke({}, any.as_ref()).cast(), 6); + ASSERT_EQ(any.cast(), 6); + ASSERT_EQ(value, 8); +} + +TEST_F(MetaFunc, ArgsByConstRef) { + using namespace entt::literals; + + func_t instance{}; + auto func = entt::resolve().func("g"_hs); + entt::meta_any any{2}; + int value = 3; + + ASSERT_TRUE(func.invoke(instance, entt::make_meta(value))); + ASSERT_EQ(func_t::value, 9); + + ASSERT_TRUE(func.invoke(instance, std::as_const(any).as_ref())); + ASSERT_EQ(func_t::value, 4); +} + +TEST_F(MetaFunc, ConstInstance) { + using namespace entt::literals; + + func_t instance{}; + auto any = entt::resolve().func("f1"_hs).invoke(std::as_const(instance), 2); + + ASSERT_FALSE(entt::resolve().func("g"_hs).invoke(std::as_const(instance), 42)); + ASSERT_TRUE(any); + ASSERT_EQ(any.cast(), 4); +} + +TEST_F(MetaFunc, AsVoid) { + using namespace entt::literals; + + auto func = entt::resolve().func("v"_hs); + func_t instance{}; + + ASSERT_EQ(func.invoke(instance, 42), entt::meta_any{std::in_place_type}); + ASSERT_EQ(func.ret(), entt::resolve()); + ASSERT_EQ(instance.value, 42); +} + +TEST_F(MetaFunc, AsRef) { + using namespace entt::literals; + + func_t instance{}; + auto func = entt::resolve().func("a"_hs); + func.invoke(instance).cast() = 3; + + ASSERT_EQ(func.ret(), entt::resolve()); + ASSERT_EQ(instance.value, 3); +} + +TEST_F(MetaFunc, AsConstRef) { + using namespace entt::literals; + + func_t instance{}; + auto func = entt::resolve().func("ca"_hs); + + ASSERT_EQ(func.ret(), entt::resolve()); + ASSERT_EQ(func.invoke(instance).cast(), 3); + ASSERT_EQ(func.invoke(instance).cast(), 3); +} + +TEST_F(MetaFuncDeathTest, AsConstRef) { + using namespace entt::literals; + + func_t instance{}; + auto func = entt::resolve().func("ca"_hs); + + ASSERT_DEATH((func.invoke(instance).cast() = 3), ""); +} + +TEST_F(MetaFunc, InvokeBaseFunction) { + using namespace entt::literals; + + auto type = entt::resolve(); + derived_t instance{}; + + ASSERT_TRUE(type.func("setter"_hs)); + ASSERT_EQ(instance.value, 3); + + type.func("setter"_hs).invoke(instance, 42); + + ASSERT_EQ(instance.value, 42); +} + +TEST_F(MetaFunc, InvokeFromBase) { + using namespace entt::literals; + + auto type = entt::resolve(); + derived_t instance{}; + + auto setter_from_base = type.func("setter_from_base"_hs); + + ASSERT_TRUE(setter_from_base); + ASSERT_EQ(instance.value, 3); + + setter_from_base.invoke(instance, 42); + + ASSERT_EQ(instance.value, 42); + + auto getter_from_base = type.func("getter_from_base"_hs); + + ASSERT_TRUE(getter_from_base); + ASSERT_EQ(getter_from_base.invoke(instance).cast(), 42); + + auto static_setter_from_base = type.func("static_setter_from_base"_hs); + + ASSERT_TRUE(static_setter_from_base); + ASSERT_EQ(instance.value, 42); + + static_setter_from_base.invoke(instance, 3); + + ASSERT_EQ(instance.value, 3); +} + +TEST_F(MetaFunc, ExternalMemberFunction) { + using namespace entt::literals; + + auto func = entt::resolve().func("emplace"_hs); + + ASSERT_TRUE(func); + ASSERT_EQ(func.id(), "emplace"_hs); + ASSERT_EQ(func.arity(), 2u); + ASSERT_FALSE(func.is_const()); + ASSERT_TRUE(func.is_static()); + ASSERT_EQ(func.ret(), entt::resolve()); + ASSERT_EQ(func.arg(0u), entt::resolve()); + ASSERT_EQ(func.arg(1u), entt::resolve()); + ASSERT_FALSE(func.arg(2u)); + + entt::registry registry; + const auto entity = registry.create(); + + ASSERT_FALSE(registry.all_of(entity)); + + func.invoke({}, entt::forward_as_meta(registry), entity); + + ASSERT_TRUE(registry.all_of(entity)); +} + +TEST_F(MetaFunc, ReRegistration) { + using namespace entt::literals; + + ASSERT_EQ(reset_and_check(), 0u); + + func_t instance{}; + auto type = entt::resolve(); + + ASSERT_TRUE(type.func("f2"_hs)); + ASSERT_FALSE(type.invoke("f2"_hs, instance, 0)); + ASSERT_TRUE(type.invoke("f2"_hs, instance, 0, 0)); + + ASSERT_TRUE(type.func("f1"_hs)); + ASSERT_TRUE(type.invoke("f1"_hs, instance, 0)); + ASSERT_FALSE(type.invoke("f1"_hs, instance, 0, 0)); + + entt::meta() + .func(&func_t::f)>("f"_hs) + .func(&func_t::f)>("f"_hs); + + ASSERT_FALSE(type.func("f1"_hs)); + ASSERT_FALSE(type.func("f2"_hs)); + ASSERT_TRUE(type.func("f"_hs)); + + ASSERT_TRUE(type.invoke("f"_hs, instance, 0)); + ASSERT_TRUE(type.invoke("f"_hs, instance, 0, 0)); + + ASSERT_EQ(reset_and_check(), 0u); +} diff --git a/modules/entt/test/entt/meta/meta_handle.cpp b/modules/entt/test/entt/meta/meta_handle.cpp new file mode 100644 index 0000000..73d862e --- /dev/null +++ b/modules/entt/test/entt/meta/meta_handle.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include + +struct clazz_t { + clazz_t() + : value{} {} + + void incr() { + ++value; + } + + void decr() { + --value; + } + + int value; +}; + +struct MetaHandle: ::testing::Test { + void SetUp() override { + using namespace entt::literals; + + entt::meta() + .type("clazz"_hs) + .func<&clazz_t::incr>("incr"_hs) + .func<&clazz_t::decr>("decr"_hs); + } + + void TearDown() override { + entt::meta_reset(); + } +}; + +TEST_F(MetaHandle, Functionalities) { + using namespace entt::literals; + + clazz_t instance{}; + entt::meta_handle handle{}; + + ASSERT_FALSE(handle); + + handle = entt::meta_handle{instance}; + + ASSERT_TRUE(handle); + ASSERT_TRUE(handle->invoke("incr"_hs)); + ASSERT_EQ(instance.value, 1); + + auto any = entt::forward_as_meta(instance); + handle = entt::meta_handle{any}; + + ASSERT_FALSE(std::as_const(handle)->invoke("decr"_hs)); + ASSERT_TRUE(handle->invoke("decr"_hs)); + ASSERT_EQ(instance.value, 0); +} diff --git a/modules/entt/test/entt/meta/meta_pointer.cpp b/modules/entt/test/entt/meta/meta_pointer.cpp new file mode 100644 index 0000000..a436a93 --- /dev/null +++ b/modules/entt/test/entt/meta/meta_pointer.cpp @@ -0,0 +1,413 @@ +#include +#include +#include +#include +#include +#include +#include + +template +struct wrapped_shared_ptr { + wrapped_shared_ptr(Type init) + : ptr{new Type{init}} {} + + Type &deref() const { + return *ptr; + } + +private: + std::shared_ptr ptr; +}; + +struct self_ptr { + using element_type = self_ptr; + + self_ptr(int v) + : value{v} {} + + const self_ptr &operator*() const { + return *this; + } + + int value; +}; + +struct proxy_ptr { + using element_type = proxy_ptr; + + proxy_ptr(int &v) + : value{&v} {} + + proxy_ptr operator*() const { + return *this; + } + + int *value; +}; + +template +struct adl_wrapped_shared_ptr: wrapped_shared_ptr {}; + +template +struct spec_wrapped_shared_ptr: wrapped_shared_ptr {}; + +template +struct entt::is_meta_pointer_like>: std::true_type {}; + +template +struct entt::is_meta_pointer_like>: std::true_type {}; + +template<> +struct entt::is_meta_pointer_like: std::true_type {}; + +template<> +struct entt::is_meta_pointer_like: std::true_type {}; + +template +struct entt::adl_meta_pointer_like> { + static decltype(auto) dereference(const spec_wrapped_shared_ptr &ptr) { + return ptr.deref(); + } +}; + +template +Type &dereference_meta_pointer_like(const adl_wrapped_shared_ptr &ptr) { + return ptr.deref(); +} + +int test_function() { + return 42; +} + +struct not_copyable_t { + not_copyable_t() = default; + not_copyable_t(const not_copyable_t &) = delete; + not_copyable_t(not_copyable_t &&) = default; + not_copyable_t &operator=(const not_copyable_t &) = delete; + not_copyable_t &operator=(not_copyable_t &&) = default; +}; + +TEST(MetaPointerLike, DereferenceOperatorInvalidType) { + int value = 0; + entt::meta_any any{value}; + + ASSERT_FALSE(any.type().is_pointer()); + ASSERT_FALSE(any.type().is_pointer_like()); + ASSERT_EQ(any.type(), entt::resolve()); + + auto deref = *any; + + ASSERT_FALSE(deref); +} + +TEST(MetaPointerLike, DereferenceOperatorConstType) { + const int value = 42; + entt::meta_any any{&value}; + + ASSERT_TRUE(any.type().is_pointer()); + ASSERT_TRUE(any.type().is_pointer_like()); + ASSERT_EQ(any.type(), entt::resolve()); + + auto deref = *any; + + ASSERT_TRUE(deref); + ASSERT_FALSE(deref.type().is_pointer()); + ASSERT_FALSE(deref.type().is_pointer_like()); + ASSERT_EQ(deref.type(), entt::resolve()); + + ASSERT_EQ(deref.try_cast(), nullptr); + ASSERT_EQ(deref.try_cast(), &value); + ASSERT_EQ(deref.cast(), 42); +} + +TEST(MetaPointerLikeDeathTest, DereferenceOperatorConstType) { + const int value = 42; + entt::meta_any any{&value}; + auto deref = *any; + + ASSERT_TRUE(deref); + ASSERT_DEATH(deref.cast() = 0, ""); +} + +TEST(MetaPointerLike, DereferenceOperatorConstAnyNonConstType) { + int value = 42; + const entt::meta_any any{&value}; + auto deref = *any; + + ASSERT_TRUE(deref); + ASSERT_FALSE(deref.type().is_pointer()); + ASSERT_FALSE(deref.type().is_pointer_like()); + ASSERT_EQ(deref.type(), entt::resolve()); + + ASSERT_NE(deref.try_cast(), nullptr); + ASSERT_NE(deref.try_cast(), nullptr); + ASSERT_EQ(deref.cast(), 42); + ASSERT_EQ(deref.cast(), 42); +} + +TEST(MetaPointerLike, DereferenceOperatorConstAnyConstType) { + const int value = 42; + const entt::meta_any any{&value}; + auto deref = *any; + + ASSERT_TRUE(deref); + ASSERT_FALSE(deref.type().is_pointer()); + ASSERT_FALSE(deref.type().is_pointer_like()); + ASSERT_EQ(deref.type(), entt::resolve()); + + ASSERT_EQ(deref.try_cast(), nullptr); + ASSERT_NE(deref.try_cast(), nullptr); + ASSERT_EQ(deref.cast(), 42); +} + +TEST(MetaPointerLikeDeathTest, DereferenceOperatorConstAnyConstType) { + const int value = 42; + const entt::meta_any any{&value}; + auto deref = *any; + + ASSERT_TRUE(deref); + ASSERT_DEATH(deref.cast() = 0, ""); +} + +TEST(MetaPointerLike, DereferenceOperatorRawPointer) { + int value = 0; + entt::meta_any any{&value}; + + ASSERT_TRUE(any.type().is_pointer()); + ASSERT_TRUE(any.type().is_pointer_like()); + ASSERT_EQ(any.type(), entt::resolve()); + + auto deref = *any; + + ASSERT_TRUE(deref); + ASSERT_FALSE(deref.type().is_pointer()); + ASSERT_FALSE(deref.type().is_pointer_like()); + ASSERT_EQ(deref.type(), entt::resolve()); + + deref.cast() = 42; + + ASSERT_EQ(*any.cast(), 42); + ASSERT_EQ(value, 42); +} + +TEST(MetaPointerLike, DereferenceOperatorSmartPointer) { + auto value = std::make_shared(0); + entt::meta_any any{value}; + + ASSERT_FALSE(any.type().is_pointer()); + ASSERT_TRUE(any.type().is_pointer_like()); + ASSERT_EQ(any.type(), entt::resolve>()); + + auto deref = *any; + + ASSERT_TRUE(deref); + ASSERT_FALSE(deref.type().is_pointer()); + ASSERT_FALSE(deref.type().is_pointer_like()); + ASSERT_EQ(deref.type(), entt::resolve()); + + deref.cast() = 42; + + ASSERT_EQ(*any.cast>(), 42); + ASSERT_EQ(*value, 42); +} + +TEST(MetaPointerLike, PointerToConstMoveOnlyType) { + const not_copyable_t instance; + entt::meta_any any{&instance}; + auto deref = *any; + + ASSERT_TRUE(any); + ASSERT_TRUE(deref); + + ASSERT_EQ(deref.try_cast(), nullptr); + ASSERT_NE(deref.try_cast(), nullptr); + ASSERT_EQ(&deref.cast(), &instance); +} + +TEST(MetaPointerLike, AsRef) { + int value = 0; + int *ptr = &value; + entt::meta_any any{entt::forward_as_meta(ptr)}; + + ASSERT_TRUE(any.type().is_pointer()); + ASSERT_TRUE(any.type().is_pointer_like()); + ASSERT_EQ(any.type(), entt::resolve()); + + auto deref = *any; + + ASSERT_TRUE(deref); + ASSERT_FALSE(deref.type().is_pointer()); + ASSERT_FALSE(deref.type().is_pointer_like()); + ASSERT_EQ(deref.type(), entt::resolve()); + + deref.cast() = 42; + + ASSERT_EQ(*any.cast(), 42); + ASSERT_EQ(value, 42); +} + +TEST(MetaPointerLike, AsConstRef) { + int value = 42; + int *const ptr = &value; + entt::meta_any any{entt::forward_as_meta(ptr)}; + + ASSERT_TRUE(any.type().is_pointer()); + ASSERT_TRUE(any.type().is_pointer_like()); + ASSERT_EQ(any.type(), entt::resolve()); + + auto deref = *any; + + ASSERT_TRUE(deref); + ASSERT_FALSE(deref.type().is_pointer()); + ASSERT_FALSE(deref.type().is_pointer_like()); + ASSERT_EQ(deref.type(), entt::resolve()); + + deref.cast() = 42; + + ASSERT_EQ(*any.cast(), 42); + ASSERT_EQ(value, 42); +} + +TEST(MetaPointerLike, DereferenceOverload) { + auto test = [](entt::meta_any any) { + ASSERT_FALSE(any.type().is_pointer()); + ASSERT_TRUE(any.type().is_pointer_like()); + + auto deref = *any; + + ASSERT_TRUE(deref); + ASSERT_FALSE(deref.type().is_pointer()); + ASSERT_FALSE(deref.type().is_pointer_like()); + ASSERT_EQ(deref.type(), entt::resolve()); + + ASSERT_EQ(deref.cast(), 42); + ASSERT_EQ(deref.cast(), 42); + }; + + test(adl_wrapped_shared_ptr{42}); + test(spec_wrapped_shared_ptr{42}); +} + +TEST(MetaPointerLike, DereferencePointerToConstOverload) { + auto test = [](entt::meta_any any) { + ASSERT_FALSE(any.type().is_pointer()); + ASSERT_TRUE(any.type().is_pointer_like()); + + auto deref = *any; + + ASSERT_TRUE(deref); + ASSERT_FALSE(deref.type().is_pointer()); + ASSERT_FALSE(deref.type().is_pointer_like()); + ASSERT_EQ(deref.type(), entt::resolve()); + ASSERT_EQ(deref.cast(), 42); + }; + + test(adl_wrapped_shared_ptr{42}); + test(spec_wrapped_shared_ptr{42}); +} + +TEST(MetaPointerLikeDeathTest, DereferencePointerToConstOverload) { + auto test = [](entt::meta_any any) { + auto deref = *any; + + ASSERT_TRUE(deref); + ASSERT_DEATH(deref.cast() = 42, ""); + }; + + test(adl_wrapped_shared_ptr{42}); + test(spec_wrapped_shared_ptr{42}); +} + +TEST(MetaPointerLike, DereferencePointerToVoid) { + auto test = [](entt::meta_any any) { + ASSERT_TRUE(any.type().is_pointer()); + ASSERT_TRUE(any.type().is_pointer_like()); + + auto deref = *any; + + ASSERT_FALSE(deref); + }; + + test(static_cast(nullptr)); + test(static_cast(nullptr)); +} + +TEST(MetaPointerLike, DereferenceSmartPointerToVoid) { + auto test = [](entt::meta_any any) { + ASSERT_TRUE(any.type().is_class()); + ASSERT_FALSE(any.type().is_pointer()); + ASSERT_TRUE(any.type().is_pointer_like()); + + auto deref = *any; + + ASSERT_FALSE(deref); + }; + + test(std::shared_ptr{}); + test(std::unique_ptr{nullptr, nullptr}); +} + +TEST(MetaPointerLike, DereferencePointerToFunction) { + auto test = [](entt::meta_any any) { + ASSERT_TRUE(any.type().is_pointer()); + ASSERT_TRUE(any.type().is_pointer_like()); + ASSERT_NE(any.try_cast(), nullptr); + ASSERT_EQ(any.cast()(), 42); + }; + + entt::meta_any func{&test_function}; + + test(func); + test(*func); + test(**func); + test(*std::as_const(func)); +} + +TEST(MetaPointerLike, DereferenceSelfPointer) { + self_ptr obj{42}; + entt::meta_any any{entt::forward_as_meta(obj)}; + entt::meta_any deref = *any; + + ASSERT_TRUE(deref); + ASSERT_TRUE(any.type().is_pointer_like()); + ASSERT_EQ(deref.cast().value, obj.value); + ASSERT_FALSE(deref.try_cast()); +} + +TEST(MetaPointerLike, DereferenceProxyPointer) { + int value = 3; + proxy_ptr obj{value}; + entt::meta_any any{obj}; + entt::meta_any deref = *any; + + ASSERT_TRUE(deref); + ASSERT_TRUE(any.type().is_pointer_like()); + ASSERT_EQ(*deref.cast().value, value); + ASSERT_TRUE(deref.try_cast()); + + *deref.cast().value = 42; + + ASSERT_EQ(value, 42); +} + +TEST(MetaPointerLike, DereferenceArray) { + entt::meta_any array{std::in_place_type}; + entt::meta_any array_of_array{std::in_place_type}; + + ASSERT_EQ(array.type(), entt::resolve()); + ASSERT_EQ(array_of_array.type(), entt::resolve()); + + ASSERT_FALSE(*array); + ASSERT_FALSE(*array_of_array); +} + +TEST(MetaPointerLike, DereferenceVerifiableNullPointerLike) { + auto test = [](entt::meta_any any) { + ASSERT_TRUE(any); + ASSERT_FALSE(*any); + }; + + test(entt::meta_any{static_cast(nullptr)}); + test(entt::meta_any{std::shared_ptr{}}); + test(entt::meta_any{std::unique_ptr{}}); +} diff --git a/modules/entt/test/entt/meta/meta_prop.cpp b/modules/entt/test/entt/meta/meta_prop.cpp new file mode 100644 index 0000000..48fef15 --- /dev/null +++ b/modules/entt/test/entt/meta/meta_prop.cpp @@ -0,0 +1,106 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +struct base_1_t {}; +struct base_2_t {}; +struct base_3_t {}; +struct derived_t: base_1_t, base_2_t, base_3_t {}; + +struct MetaProp: ::testing::Test { + void SetUp() override { + using namespace entt::literals; + + entt::meta() + .type("base_1"_hs) + .prop("int"_hs, 42); + + entt::meta() + .type("base_2"_hs) + .props(std::make_pair("bool"_hs, false), std::make_pair("char[]"_hs, "char[]")); + + entt::meta() + .type("base_3"_hs) + .prop(std::make_tuple("key_only"_hs, std::make_pair("key"_hs, 42))); + + entt::meta() + .type("derived"_hs) + .base() + .base() + .base(); + } + + void TearDown() override { + entt::meta_reset(); + } +}; + +TEST_F(MetaProp, Functionalities) { + using namespace entt::literals; + + auto prop = entt::resolve().prop("int"_hs); + + ASSERT_TRUE(prop); + ASSERT_EQ(prop.key(), "int"_hs); + ASSERT_EQ(prop.value(), 42); +} + +TEST_F(MetaProp, FromBase) { + using namespace entt::literals; + + auto type = entt::resolve(); + auto prop_bool = type.prop("bool"_hs); + auto prop_int = type.prop("int"_hs); + auto key_only = type.prop("key_only"_hs); + auto key_value = type.prop("key"_hs); + + ASSERT_TRUE(prop_bool); + ASSERT_TRUE(prop_int); + ASSERT_TRUE(key_only); + ASSERT_TRUE(key_value); + + ASSERT_FALSE(prop_bool.value().cast()); + ASSERT_EQ(prop_int.value().cast(), 42); + ASSERT_FALSE(key_only.value()); + ASSERT_EQ(key_value.value().cast(), 42); +} + +TEST_F(MetaProp, DeducedArrayType) { + using namespace entt::literals; + + auto prop = entt::resolve().prop("char[]"_hs); + + ASSERT_TRUE(prop); + ASSERT_EQ(prop.key(), "char[]"_hs); + ASSERT_EQ(prop.value().type(), entt::resolve()); + ASSERT_EQ(strcmp(prop.value().cast(), "char[]"), 0); +} + +TEST_F(MetaProp, ReRegistration) { + using namespace entt::literals; + + SetUp(); + + auto *node = entt::internal::meta_node::resolve(); + auto type = entt::resolve(); + + ASSERT_NE(node->prop, nullptr); + ASSERT_EQ(node->prop->next, nullptr); + + ASSERT_TRUE(type.prop("int"_hs)); + ASSERT_EQ(type.prop("int"_hs).value().cast(), 42); + + entt::meta().prop("double"_hs, 3.); + + ASSERT_NE(node->prop, nullptr); + ASSERT_EQ(node->prop->next, nullptr); + + ASSERT_FALSE(type.prop("int"_hs)); + ASSERT_TRUE(type.prop("double"_hs)); + ASSERT_EQ(type.prop("double"_hs).value().cast(), 3.); +} diff --git a/modules/entt/test/entt/meta/meta_range.cpp b/modules/entt/test/entt/meta/meta_range.cpp new file mode 100644 index 0000000..03a1501 --- /dev/null +++ b/modules/entt/test/entt/meta/meta_range.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include +#include + +struct MetaRange: ::testing::Test { + void SetUp() override { + using namespace entt::literals; + + entt::meta().type("int"_hs); + entt::meta().type("double"_hs); + } + + void TearDown() override { + entt::meta_reset(); + } +}; + +TEST_F(MetaRange, Range) { + using namespace entt::literals; + + entt::meta_range range{entt::internal::meta_context::local()}; + auto it = range.begin(); + + ASSERT_NE(it, range.end()); + ASSERT_TRUE(it != range.end()); + ASSERT_FALSE(it == range.end()); + + ASSERT_EQ(it->info(), entt::resolve().info()); + ASSERT_EQ((++it)->info(), entt::resolve("int"_hs).info()); + ASSERT_EQ((it++)->info(), entt::resolve().info()); + + ASSERT_EQ(it, range.end()); +} + +TEST_F(MetaRange, EmptyRange) { + entt::meta_range range{}; + ASSERT_EQ(range.begin(), range.end()); +} + +TEST_F(MetaRange, IteratorConversion) { + using namespace entt::literals; + + entt::meta_range range{entt::internal::meta_context::local()}; + typename decltype(range)::iterator it = range.begin(); + typename decltype(range)::const_iterator cit = it; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + + ASSERT_EQ(*it, entt::resolve()); + ASSERT_EQ(*it, *cit); + + ASSERT_EQ(it, cit); + ASSERT_NE(++cit, it); +} diff --git a/modules/entt/test/entt/meta/meta_template.cpp b/modules/entt/test/entt/meta/meta_template.cpp new file mode 100644 index 0000000..5f86e1d --- /dev/null +++ b/modules/entt/test/entt/meta/meta_template.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include + +template +struct function_type; + +template +struct function_type {}; + +template +struct entt::meta_template_traits> { + using class_type = meta_class_template_tag; + using args_type = type_list; +}; + +TEST(MetaTemplate, Invalid) { + const auto type = entt::resolve(); + + ASSERT_FALSE(type.is_template_specialization()); + ASSERT_EQ(type.template_arity(), 0u); + ASSERT_EQ(type.template_type(), entt::meta_type{}); + ASSERT_EQ(type.template_arg(0u), entt::meta_type{}); +} + +TEST(MetaTemplate, Valid) { + const auto type = entt::resolve>(); + + ASSERT_TRUE(type.is_template_specialization()); + ASSERT_EQ(type.template_arity(), 2u); + ASSERT_EQ(type.template_type(), entt::resolve>()); + ASSERT_EQ(type.template_arg(0u), entt::resolve()); + ASSERT_EQ(type.template_arg(1u), entt::resolve()); + ASSERT_EQ(type.template_arg(2u), entt::meta_type{}); +} + +TEST(MetaTemplate, CustomTraits) { + const auto type = entt::resolve>(); + + ASSERT_TRUE(type.is_template_specialization()); + ASSERT_EQ(type.template_arity(), 3u); + ASSERT_EQ(type.template_type(), entt::resolve>()); + ASSERT_EQ(type.template_arg(0u), entt::resolve()); + ASSERT_EQ(type.template_arg(1u), entt::resolve()); + ASSERT_EQ(type.template_arg(2u), entt::resolve()); + ASSERT_EQ(type.template_arg(3u), entt::meta_type{}); +} diff --git a/modules/entt/test/entt/meta/meta_type.cpp b/modules/entt/test/entt/meta/meta_type.cpp new file mode 100644 index 0000000..47c8992 --- /dev/null +++ b/modules/entt/test/entt/meta/meta_type.cpp @@ -0,0 +1,683 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +void set(Type &prop, Type value) { + prop = value; +} + +template +Type get(Type &prop) { + return prop; +} + +struct base_t { + base_t() + : value{'c'} {}; + + char value; +}; + +struct derived_t: base_t { + derived_t() + : base_t{} {} +}; + +struct abstract_t { + virtual ~abstract_t() = default; + + virtual void func(int) {} + void base_only(int) {} +}; + +struct concrete_t: base_t, abstract_t { + void func(int v) override { + abstract_t::func(v); + value = v; + } + + int value{3}; +}; + +struct clazz_t { + clazz_t() = default; + + clazz_t(const base_t &, int v) + : value{v} {} + + void member() {} + static void func() {} + + operator int() const { + return value; + } + + int value; +}; + +struct overloaded_func_t { + int e(int v) const { + return v + v; + } + + int f(const base_t &, int a, int b) { + return f(a, b); + } + + int f(int a, int b) { + value = a; + return g(b); + } + + int f(int v) const { + return g(v); + } + + float f(int a, float b) { + value = a; + return static_cast(e(static_cast(b))); + } + + int g(int v) const { + return v * v; + } + + inline static int value = 0; +}; + +enum class property_t { + random, + value, + key_only, + list +}; + +struct MetaType: ::testing::Test { + void SetUp() override { + using namespace entt::literals; + + entt::meta() + .type("double"_hs) + .data, get>("var"_hs); + + entt::meta() + .type("unsigned int"_hs) + .data<0u>("min"_hs) + .data<100u>("max"_hs); + + entt::meta() + .type("base"_hs) + .data<&base_t::value>("value"_hs); + + entt::meta() + .type("derived"_hs) + .base(); + + entt::meta() + .type("abstract"_hs) + .func<&abstract_t::func>("func"_hs) + .func<&abstract_t::base_only>("base_only"_hs); + + entt::meta() + .type("concrete"_hs) + .base() + .base(); + + entt::meta() + .type("overloaded_func"_hs) + .func<&overloaded_func_t::e>("e"_hs) + .func(&overloaded_func_t::f)>("f"_hs) + .func(&overloaded_func_t::f)>("f"_hs) + .func(&overloaded_func_t::f)>("f"_hs) + .func(&overloaded_func_t::f)>("f"_hs) + .func<&overloaded_func_t::g>("g"_hs); + + entt::meta() + .type("property"_hs) + .data("random"_hs) + .props(std::make_pair(property_t::random, 0), std::make_pair(property_t::value, 3)) + .data("value"_hs) + .props(std::make_pair(property_t::random, true), std::make_pair(property_t::value, 0), property_t::key_only, property_t::list) + .data("key_only"_hs) + .prop(property_t::key_only) + .data("list"_hs) + .props(std::make_pair(property_t::random, false), std::make_pair(property_t::value, 0), property_t::key_only) + .data, get>("var"_hs); + + entt::meta() + .type("clazz"_hs) + .prop(property_t::value, 42) + .ctor() + .data<&clazz_t::value>("value"_hs) + .func<&clazz_t::member>("member"_hs) + .func("func"_hs) + .conv(); + } + + void TearDown() override { + entt::meta_reset(); + } +}; + +using MetaTypeDeathTest = MetaType; + +TEST_F(MetaType, Resolve) { + using namespace entt::literals; + + ASSERT_EQ(entt::resolve(), entt::resolve("double"_hs)); + ASSERT_EQ(entt::resolve(), entt::resolve(entt::type_id())); + ASSERT_FALSE(entt::resolve(entt::type_id())); + + auto range = entt::resolve(); + // it could be "char"_hs rather than entt::hashed_string::value("char") if it weren't for a bug in VS2017 + const auto it = std::find_if(range.begin(), range.end(), [](auto type) { return type.id() == entt::hashed_string::value("clazz"); }); + + ASSERT_NE(it, range.end()); + ASSERT_EQ(*it, entt::resolve()); + + bool found = false; + + for(auto curr: entt::resolve()) { + found = found || curr == entt::resolve(); + } + + ASSERT_TRUE(found); +} + +TEST_F(MetaType, Functionalities) { + using namespace entt::literals; + + auto type = entt::resolve(); + + ASSERT_TRUE(type); + ASSERT_NE(type, entt::meta_type{}); + ASSERT_EQ(type.id(), "clazz"_hs); + ASSERT_EQ(type.info(), entt::type_id()); + + for(auto curr: type.prop()) { + ASSERT_EQ(curr.key(), property_t::value); + ASSERT_EQ(curr.value(), 42); + } + + ASSERT_FALSE(type.prop(property_t::key_only)); + ASSERT_FALSE(type.prop("property"_hs)); + + auto prop = type.prop(property_t::value); + + ASSERT_TRUE(prop); + ASSERT_EQ(prop.key(), property_t::value); + ASSERT_EQ(prop.value(), 42); +} + +TEST_F(MetaType, SizeOf) { + ASSERT_EQ(entt::resolve().size_of(), 0u); + ASSERT_EQ(entt::resolve().size_of(), sizeof(int)); + ASSERT_EQ(entt::resolve().size_of(), 0u); + ASSERT_EQ(entt::resolve().size_of(), sizeof(int[3])); +} + +TEST_F(MetaType, Traits) { + ASSERT_TRUE(entt::resolve().is_arithmetic()); + ASSERT_TRUE(entt::resolve().is_arithmetic()); + ASSERT_FALSE(entt::resolve().is_arithmetic()); + + ASSERT_TRUE(entt::resolve().is_array()); + ASSERT_TRUE(entt::resolve().is_array()); + ASSERT_FALSE(entt::resolve().is_array()); + + ASSERT_TRUE(entt::resolve().is_enum()); + ASSERT_FALSE(entt::resolve().is_enum()); + + ASSERT_TRUE(entt::resolve().is_class()); + ASSERT_FALSE(entt::resolve().is_class()); + + ASSERT_TRUE(entt::resolve().is_pointer()); + ASSERT_FALSE(entt::resolve().is_pointer()); + + ASSERT_TRUE(entt::resolve().is_pointer_like()); + ASSERT_TRUE(entt::resolve>().is_pointer_like()); + ASSERT_FALSE(entt::resolve().is_pointer_like()); + + ASSERT_FALSE((entt::resolve().is_sequence_container())); + ASSERT_TRUE(entt::resolve>().is_sequence_container()); + ASSERT_FALSE((entt::resolve>().is_sequence_container())); + + ASSERT_FALSE((entt::resolve().is_associative_container())); + ASSERT_TRUE((entt::resolve>().is_associative_container())); + ASSERT_FALSE(entt::resolve>().is_associative_container()); +} + +TEST_F(MetaType, RemovePointer) { + ASSERT_EQ(entt::resolve().remove_pointer(), entt::resolve()); + ASSERT_EQ(entt::resolve().remove_pointer(), entt::resolve()); + ASSERT_EQ(entt::resolve().remove_pointer(), entt::resolve()); + ASSERT_EQ(entt::resolve().remove_pointer(), entt::resolve()); +} + +TEST_F(MetaType, TemplateInfo) { + ASSERT_FALSE(entt::resolve().is_template_specialization()); + ASSERT_EQ(entt::resolve().template_arity(), 0u); + ASSERT_EQ(entt::resolve().template_type(), entt::meta_type{}); + ASSERT_EQ(entt::resolve().template_arg(0u), entt::meta_type{}); + + ASSERT_TRUE(entt::resolve>().is_template_specialization()); + ASSERT_EQ(entt::resolve>().template_arity(), 1u); + ASSERT_EQ(entt::resolve>().template_type(), entt::resolve>()); + ASSERT_EQ(entt::resolve>().template_arg(0u), entt::resolve()); + ASSERT_EQ(entt::resolve>().template_arg(1u), entt::meta_type{}); +} + +TEST_F(MetaType, Base) { + using namespace entt::literals; + + auto type = entt::resolve(); + bool iterate = false; + + for(auto curr: type.base()) { + ASSERT_EQ(curr, entt::resolve()); + iterate = true; + } + + ASSERT_TRUE(iterate); + ASSERT_EQ(type.base("base"_hs), entt::resolve()); + ASSERT_FALSE(type.base("esabe"_hs)); +} + +TEST_F(MetaType, Ctor) { + derived_t derived; + base_t &base = derived; + auto type = entt::resolve(); + + ASSERT_TRUE((type.construct(entt::forward_as_meta(derived), 42))); + ASSERT_TRUE((type.construct(entt::forward_as_meta(base), 42))); + + // use the implicitly generated default constructor + auto any = type.construct(); + + ASSERT_TRUE(any); + ASSERT_EQ(any.type(), entt::resolve()); +} + +TEST_F(MetaType, Data) { + using namespace entt::literals; + + auto type = entt::resolve(); + int counter{}; + + for([[maybe_unused]] auto curr: type.data()) { + ++counter; + } + + ASSERT_EQ(counter, 1); + ASSERT_TRUE(type.data("value"_hs)); +} + +TEST_F(MetaType, Func) { + using namespace entt::literals; + + auto type = entt::resolve(); + clazz_t instance{}; + int counter{}; + + for([[maybe_unused]] auto curr: type.func()) { + ++counter; + } + + ASSERT_EQ(counter, 2); + ASSERT_TRUE(type.func("member"_hs)); + ASSERT_TRUE(type.func("func"_hs)); + ASSERT_TRUE(type.func("member"_hs).invoke(instance)); + ASSERT_TRUE(type.func("func"_hs).invoke({})); +} + +TEST_F(MetaType, Invoke) { + using namespace entt::literals; + + auto type = entt::resolve(); + clazz_t instance{}; + + ASSERT_TRUE(type.invoke("member"_hs, instance)); + ASSERT_FALSE(type.invoke("rebmem"_hs, {})); +} + +TEST_F(MetaType, InvokeFromBase) { + using namespace entt::literals; + + auto type = entt::resolve(); + concrete_t instance{}; + + ASSERT_TRUE(type.invoke("base_only"_hs, instance, 42)); + ASSERT_FALSE(type.invoke("ylno_esab"_hs, {}, 'c')); +} + +TEST_F(MetaType, OverloadedFunc) { + using namespace entt::literals; + + const auto type = entt::resolve(); + overloaded_func_t instance{}; + + ASSERT_TRUE(type.func("f"_hs)); + ASSERT_TRUE(type.func("e"_hs)); + ASSERT_TRUE(type.func("g"_hs)); + + const auto first = type.invoke("f"_hs, instance, base_t{}, 1, 2); + + ASSERT_TRUE(first); + ASSERT_EQ(overloaded_func_t::value, 1); + ASSERT_NE(first.try_cast(), nullptr); + ASSERT_EQ(first.cast(), 4); + + const auto second = type.invoke("f"_hs, instance, 3, 4); + + ASSERT_TRUE(second); + ASSERT_EQ(overloaded_func_t::value, 3); + ASSERT_NE(second.try_cast(), nullptr); + ASSERT_EQ(second.cast(), 16); + + const auto third = type.invoke("f"_hs, instance, 5); + + ASSERT_TRUE(third); + ASSERT_EQ(overloaded_func_t::value, 3); + ASSERT_NE(third.try_cast(), nullptr); + ASSERT_EQ(third.cast(), 25); + + const auto fourth = type.invoke("f"_hs, instance, 6, 7.f); + + ASSERT_TRUE(fourth); + ASSERT_EQ(overloaded_func_t::value, 6); + ASSERT_NE(fourth.try_cast(), nullptr); + ASSERT_EQ(fourth.cast(), 14.f); + + const auto cast = type.invoke("f"_hs, instance, 8, 9.f); + + ASSERT_TRUE(cast); + ASSERT_EQ(overloaded_func_t::value, 8); + ASSERT_NE(cast.try_cast(), nullptr); + ASSERT_EQ(cast.cast(), 18.f); + + const auto ambiguous = type.invoke("f"_hs, instance, 8, 9.); + + ASSERT_FALSE(ambiguous); +} + +TEST_F(MetaType, Construct) { + auto any = entt::resolve().construct(base_t{}, 42); + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast().value, 42); +} + +TEST_F(MetaType, ConstructNoArgs) { + // this should work, no other tests required + auto any = entt::resolve().construct(); + + ASSERT_TRUE(any); +} + +TEST_F(MetaType, ConstructMetaAnyArgs) { + auto any = entt::resolve().construct(entt::meta_any{base_t{}}, entt::meta_any{42}); + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast().value, 42); +} + +TEST_F(MetaType, ConstructInvalidArgs) { + ASSERT_FALSE(entt::resolve().construct('c', base_t{})); +} + +TEST_F(MetaType, LessArgs) { + ASSERT_FALSE(entt::resolve().construct(base_t{})); +} + +TEST_F(MetaType, ConstructCastAndConvert) { + auto any = entt::resolve().construct(derived_t{}, clazz_t{derived_t{}, 42}); + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast().value, 42); +} + +TEST_F(MetaType, ConstructArithmeticConversion) { + auto any = entt::resolve().construct(derived_t{}, clazz_t{derived_t{}, true}); + + ASSERT_TRUE(any); + ASSERT_EQ(any.cast().value, 1); +} + +TEST_F(MetaType, Reset) { + using namespace entt::literals; + + ASSERT_TRUE(entt::resolve("clazz"_hs)); + ASSERT_EQ(entt::resolve().id(), "clazz"_hs); + ASSERT_TRUE(entt::resolve().prop(property_t::value)); + ASSERT_TRUE(entt::resolve().data("value"_hs)); + ASSERT_TRUE((entt::resolve().construct(derived_t{}, clazz_t{}))); + // implicitly generated default constructor + ASSERT_TRUE(entt::resolve().construct()); + + entt::meta_reset("clazz"_hs); + + ASSERT_FALSE(entt::resolve("clazz"_hs)); + ASSERT_NE(entt::resolve().id(), "clazz"_hs); + ASSERT_FALSE(entt::resolve().prop(property_t::value)); + ASSERT_FALSE(entt::resolve().data("value"_hs)); + ASSERT_FALSE((entt::resolve().construct(derived_t{}, clazz_t{}))); + // implicitly generated default constructor is not cleared + ASSERT_TRUE(entt::resolve().construct()); + + entt::meta().type("clazz"_hs); + + ASSERT_TRUE(entt::resolve("clazz"_hs)); +} + +TEST_F(MetaType, ResetAll) { + using namespace entt::literals; + + ASSERT_NE(entt::resolve().begin(), entt::resolve().end()); + + ASSERT_TRUE(entt::resolve("clazz"_hs)); + ASSERT_TRUE(entt::resolve("overloaded_func"_hs)); + ASSERT_TRUE(entt::resolve("double"_hs)); + + entt::meta_reset(); + + ASSERT_FALSE(entt::resolve("clazz"_hs)); + ASSERT_FALSE(entt::resolve("overloaded_func"_hs)); + ASSERT_FALSE(entt::resolve("double"_hs)); + + ASSERT_EQ(entt::resolve().begin(), entt::resolve().end()); +} + +TEST_F(MetaType, AbstractClass) { + using namespace entt::literals; + + auto type = entt::resolve(); + concrete_t instance; + + ASSERT_EQ(type.info(), entt::type_id()); + ASSERT_EQ(instance.base_t::value, 'c'); + ASSERT_EQ(instance.value, 3); + + type.func("func"_hs).invoke(instance, 42); + + ASSERT_EQ(instance.base_t::value, 'c'); + ASSERT_EQ(instance.value, 42); +} + +TEST_F(MetaType, EnumAndNamedConstants) { + using namespace entt::literals; + + auto type = entt::resolve(); + + ASSERT_TRUE(type.data("random"_hs)); + ASSERT_TRUE(type.data("value"_hs)); + + ASSERT_EQ(type.data("random"_hs).type(), type); + ASSERT_EQ(type.data("value"_hs).type(), type); + + ASSERT_FALSE(type.data("random"_hs).set({}, property_t::value)); + ASSERT_FALSE(type.data("value"_hs).set({}, property_t::random)); + + ASSERT_EQ(type.data("random"_hs).get({}).cast(), property_t::random); + ASSERT_EQ(type.data("value"_hs).get({}).cast(), property_t::value); +} + +TEST_F(MetaType, ArithmeticTypeAndNamedConstants) { + using namespace entt::literals; + + auto type = entt::resolve(); + + ASSERT_TRUE(type.data("min"_hs)); + ASSERT_TRUE(type.data("max"_hs)); + + ASSERT_EQ(type.data("min"_hs).type(), type); + ASSERT_EQ(type.data("max"_hs).type(), type); + + ASSERT_FALSE(type.data("min"_hs).set({}, 100u)); + ASSERT_FALSE(type.data("max"_hs).set({}, 0u)); + + ASSERT_EQ(type.data("min"_hs).get({}).cast(), 0u); + ASSERT_EQ(type.data("max"_hs).get({}).cast(), 100u); +} + +TEST_F(MetaType, Variables) { + using namespace entt::literals; + + auto p_data = entt::resolve().data("var"_hs); + auto d_data = entt::resolve("double"_hs).data("var"_hs); + + property_t prop{property_t::key_only}; + double d = 3.; + + p_data.set(prop, property_t::random); + d_data.set(d, 42.); + + ASSERT_EQ(p_data.get(prop).cast(), property_t::random); + ASSERT_EQ(d_data.get(d).cast(), 42.); + ASSERT_EQ(prop, property_t::random); + ASSERT_EQ(d, 42.); +} + +TEST_F(MetaType, PropertiesAndCornerCases) { + using namespace entt::literals; + + auto type = entt::resolve(); + + ASSERT_EQ(type.data("random"_hs).prop(property_t::random).value().cast(), 0); + ASSERT_EQ(type.data("random"_hs).prop(property_t::value).value().cast(), 3); + + ASSERT_EQ(type.data("value"_hs).prop(property_t::random).value().cast(), true); + ASSERT_EQ(type.data("value"_hs).prop(property_t::value).value().cast(), 0); + ASSERT_TRUE(type.data("value"_hs).prop(property_t::key_only)); + ASSERT_FALSE(type.data("value"_hs).prop(property_t::key_only).value()); + + ASSERT_TRUE(type.data("key_only"_hs).prop(property_t::key_only)); + ASSERT_FALSE(type.data("key_only"_hs).prop(property_t::key_only).value()); + + ASSERT_EQ(type.data("list"_hs).prop(property_t::random).value().cast(), false); + ASSERT_EQ(type.data("list"_hs).prop(property_t::value).value().cast(), 0); + ASSERT_TRUE(type.data("list"_hs).prop(property_t::key_only)); + ASSERT_FALSE(type.data("list"_hs).prop(property_t::key_only).value()); +} + +TEST_F(MetaType, ResetAndReRegistrationAfterReset) { + using namespace entt::literals; + + ASSERT_NE(*entt::internal::meta_context::global(), nullptr); + + entt::meta_reset(); + entt::meta_reset(); + entt::meta_reset(); + entt::meta_reset(); + entt::meta_reset(); + entt::meta_reset(); + entt::meta_reset(); + entt::meta_reset(); + entt::meta_reset(); + + ASSERT_FALSE(entt::resolve("double"_hs)); + ASSERT_FALSE(entt::resolve("base"_hs)); + ASSERT_FALSE(entt::resolve("derived"_hs)); + ASSERT_FALSE(entt::resolve("clazz"_hs)); + + ASSERT_EQ(*entt::internal::meta_context::global(), nullptr); + + ASSERT_FALSE(entt::resolve().prop(property_t::value)); + // implicitly generated default constructor is not cleared + ASSERT_TRUE(entt::resolve().construct()); + ASSERT_FALSE(entt::resolve().data("value"_hs)); + ASSERT_FALSE(entt::resolve().func("member"_hs)); + + entt::meta().type("double"_hs); + entt::meta_any any{42.}; + + ASSERT_TRUE(any); + ASSERT_TRUE(any.allow_cast()); + ASSERT_TRUE(any.allow_cast()); + + ASSERT_FALSE(entt::resolve("derived"_hs)); + ASSERT_TRUE(entt::resolve("double"_hs)); + + entt::meta() + .type("property"_hs) + .data("rand"_hs) + .props(std::make_pair(property_t::value, 42), std::make_pair(property_t::random, 3)); + + ASSERT_TRUE(entt::resolve().data("rand"_hs).prop(property_t::value)); + ASSERT_TRUE(entt::resolve().data("rand"_hs).prop(property_t::random)); +} + +TEST_F(MetaType, ReRegistration) { + using namespace entt::literals; + + int count = 0; + + for(auto type: entt::resolve()) { + count += static_cast(type); + } + + SetUp(); + + for(auto type: entt::resolve()) { + count -= static_cast(type); + } + + ASSERT_EQ(count, 0); + ASSERT_TRUE(entt::resolve("double"_hs)); + + entt::meta().type("real"_hs); + + ASSERT_FALSE(entt::resolve("double"_hs)); + ASSERT_TRUE(entt::resolve("real"_hs)); + ASSERT_TRUE(entt::resolve("real"_hs).data("var"_hs)); +} + +TEST_F(MetaType, NameCollision) { + using namespace entt::literals; + + ASSERT_NO_FATAL_FAILURE(entt::meta().type("clazz"_hs)); + ASSERT_TRUE(entt::resolve("clazz"_hs)); + + ASSERT_NO_FATAL_FAILURE(entt::meta().type("quux"_hs)); + ASSERT_FALSE(entt::resolve("clazz"_hs)); + ASSERT_TRUE(entt::resolve("quux"_hs)); +} + +TEST_F(MetaTypeDeathTest, NameCollision) { + using namespace entt::literals; + + ASSERT_DEATH(entt::meta().type("abstract"_hs), ""); +} diff --git a/modules/entt/test/entt/meta/meta_utility.cpp b/modules/entt/test/entt/meta/meta_utility.cpp new file mode 100644 index 0000000..2ae71de --- /dev/null +++ b/modules/entt/test/entt/meta/meta_utility.cpp @@ -0,0 +1,213 @@ +#include +#include +#include +#include +#include +#include + +struct clazz { + void setter(int v) { + member = v; + } + + int getter() const { + return member; + } + + static void static_setter(clazz &instance, int v) { + instance.member = v; + } + + static int static_getter(const clazz &instance) { + return instance.member; + } + + static void reset_value() { + value = 0; + } + + static int get_value() { + return value; + } + + static clazz factory(int v) { + clazz instance{}; + instance.member = v; + return instance; + } + + int member{}; + const int cmember{}; + inline static int value{}; + inline static const int cvalue{}; + inline static int arr[3u]{}; +}; + +struct MetaUtility: ::testing::Test { + void SetUp() override { + clazz::value = 0; + } +}; + +TEST_F(MetaUtility, MetaDispatch) { + int value = 42; + + auto as_void = entt::meta_dispatch(value); + auto as_ref = entt::meta_dispatch(value); + auto as_cref = entt::meta_dispatch(value); + auto as_is = entt::meta_dispatch(value); + + ASSERT_EQ(as_void.type(), entt::resolve()); + ASSERT_EQ(as_ref.type(), entt::resolve()); + ASSERT_EQ(as_cref.type(), entt::resolve()); + ASSERT_EQ(as_is.type(), entt::resolve()); + + ASSERT_NE(as_is.try_cast(), nullptr); + ASSERT_NE(as_ref.try_cast(), nullptr); + ASSERT_EQ(as_cref.try_cast(), nullptr); + ASSERT_NE(as_cref.try_cast(), nullptr); + + ASSERT_EQ(as_is.cast(), 42); + ASSERT_EQ(as_ref.cast(), 42); + ASSERT_EQ(as_cref.cast(), 42); +} + +TEST_F(MetaUtility, MetaArg) { + ASSERT_EQ((entt::meta_arg>(0u)), entt::resolve()); + ASSERT_EQ((entt::meta_arg>(1u)), entt::resolve()); +} + +TEST_F(MetaUtility, MetaSetter) { + const int invalid{}; + clazz instance{}; + + ASSERT_FALSE((entt::meta_setter(instance, instance))); + ASSERT_FALSE((entt::meta_setter(std::as_const(instance), 42))); + ASSERT_FALSE((entt::meta_setter(invalid, 42))); + ASSERT_TRUE((entt::meta_setter(instance, 42))); + ASSERT_EQ(instance.member, 42); + + ASSERT_FALSE((entt::meta_setter(instance, instance))); + ASSERT_FALSE((entt::meta_setter(std::as_const(instance), 3))); + ASSERT_FALSE((entt::meta_setter(invalid, 3))); + ASSERT_TRUE((entt::meta_setter(instance, 3))); + ASSERT_EQ(instance.member, 3); + + ASSERT_FALSE((entt::meta_setter(instance, instance))); + ASSERT_FALSE((entt::meta_setter(invalid, 99))); + ASSERT_TRUE((entt::meta_setter(instance, 99))); + ASSERT_EQ(instance.member, 99); + + ASSERT_FALSE((entt::meta_setter(instance, 99))); + ASSERT_FALSE((entt::meta_setter(invalid, 99))); + ASSERT_EQ(instance.cmember, 0); + + ASSERT_FALSE((entt::meta_setter(instance, instance))); + ASSERT_TRUE((entt::meta_setter(invalid, 1))); + ASSERT_TRUE((entt::meta_setter(instance, 2))); + ASSERT_EQ(clazz::value, 2); + + ASSERT_FALSE((entt::meta_setter(instance, 1))); + ASSERT_FALSE((entt::meta_setter(invalid, 1))); + ASSERT_EQ(clazz::cvalue, 0); +} + +TEST_F(MetaUtility, MetaGetter) { + const int invalid{}; + clazz instance{}; + + ASSERT_FALSE((entt::meta_getter(invalid))); + ASSERT_EQ((entt::meta_getter(instance)).cast(), 0); + + ASSERT_FALSE((entt::meta_getter(invalid))); + ASSERT_EQ((entt::meta_getter(instance)).cast(), 0); + + ASSERT_FALSE((entt::meta_getter(invalid))); + ASSERT_EQ((entt::meta_getter(instance)).cast(), 0); + ASSERT_EQ((entt::meta_getter(std::as_const(instance))).cast(), 0); + + ASSERT_FALSE((entt::meta_getter(invalid))); + ASSERT_EQ((entt::meta_getter(instance)).cast(), 0); + ASSERT_EQ((entt::meta_getter(std::as_const(instance))).cast(), 0); + + ASSERT_FALSE((entt::meta_getter(invalid))); + ASSERT_FALSE((entt::meta_getter(instance))); + + ASSERT_EQ((entt::meta_getter(invalid)).cast(), 0); + ASSERT_EQ((entt::meta_getter(instance)).cast(), 0); + + ASSERT_EQ((entt::meta_getter(invalid)).cast(), 0); + ASSERT_EQ((entt::meta_getter(instance)).cast(), 0); + + ASSERT_EQ((entt::meta_getter(invalid)).cast(), 42); + ASSERT_EQ((entt::meta_getter(instance)).cast(), 42); +} + +TEST_F(MetaUtility, MetaInvokeWithCandidate) { + entt::meta_any args[2u]{clazz{}, 42}; + args[0u].cast().value = 99; + + ASSERT_FALSE((entt::meta_invoke({}, &clazz::setter, nullptr))); + ASSERT_FALSE((entt::meta_invoke({}, &clazz::getter, nullptr))); + + ASSERT_TRUE((entt::meta_invoke(args[0u], &clazz::setter, args + 1u))); + ASSERT_FALSE((entt::meta_invoke(args[0u], &clazz::setter, args))); + ASSERT_EQ((entt::meta_invoke(args[0u], &clazz::getter, nullptr)).cast(), 42); + ASSERT_FALSE((entt::meta_invoke(args[1u], &clazz::getter, nullptr))); + + ASSERT_EQ((entt::meta_invoke({}, &clazz::get_value, nullptr)).cast(), 99); + ASSERT_TRUE((entt::meta_invoke({}, &clazz::reset_value, nullptr))); + ASSERT_EQ(args[0u].cast().value, 0); +} + +TEST_F(MetaUtility, MetaInvoke) { + entt::meta_any args[2u]{clazz{}, 42}; + args[0u].cast().value = 99; + + ASSERT_FALSE((entt::meta_invoke({}, nullptr))); + ASSERT_FALSE((entt::meta_invoke({}, nullptr))); + + ASSERT_TRUE((entt::meta_invoke(args[0u], args + 1u))); + ASSERT_FALSE((entt::meta_invoke(args[0u], args))); + ASSERT_EQ((entt::meta_invoke(args[0u], nullptr)).cast(), 42); + ASSERT_FALSE((entt::meta_invoke(args[1u], nullptr))); + + ASSERT_EQ((entt::meta_invoke({}, nullptr)).cast(), 99); + ASSERT_TRUE((entt::meta_invoke({}, nullptr))); + ASSERT_EQ(args[0u].cast().value, 0); +} + +TEST_F(MetaUtility, MetaConstructArgsOnly) { + entt::meta_any args[2u]{clazz{}, 42}; + const auto any = entt::meta_construct(args + 1u); + + ASSERT_TRUE(any); + ASSERT_FALSE((entt::meta_construct(args))); + ASSERT_EQ(any.cast().member, 42); +} + +TEST_F(MetaUtility, MetaConstructWithCandidate) { + entt::meta_any args[2u]{clazz{}, 42}; + const auto any = entt::meta_construct(&clazz::factory, args + 1u); + + ASSERT_TRUE(any); + ASSERT_FALSE((entt::meta_construct(&clazz::factory, args))); + ASSERT_EQ(any.cast().member, 42); + + ASSERT_EQ(args[0u].cast().member, 0); + ASSERT_TRUE((entt::meta_construct(&clazz::static_setter, args))); + ASSERT_EQ(args[0u].cast().member, 42); +} + +TEST_F(MetaUtility, MetaConstruct) { + entt::meta_any args[2u]{clazz{}, 42}; + const auto any = entt::meta_construct(args + 1u); + + ASSERT_TRUE(any); + ASSERT_FALSE((entt::meta_construct(args))); + ASSERT_EQ(any.cast().member, 42); + + ASSERT_EQ(args[0u].cast().member, 0); + ASSERT_TRUE((entt::meta_construct(args))); + ASSERT_EQ(args[0u].cast().member, 42); +} diff --git a/modules/entt/test/entt/poly/poly.cpp b/modules/entt/test/entt/poly/poly.cpp new file mode 100644 index 0000000..d0db23e --- /dev/null +++ b/modules/entt/test/entt/poly/poly.cpp @@ -0,0 +1,416 @@ +#include +#include +#include +#include +#include +#include +#include + +template +struct common_type: Base { + void incr() { + entt::poly_call<0>(*this); + } + + void set(int v) { + entt::poly_call<1>(*this, v); + } + + int get() const { + return entt::poly_call<2>(*this); + } + + void decr() { + entt::poly_call<3>(*this); + } + + int mul(int v) const { + return entt::poly_call<4>(*this, v); + } + + int rand() const { + return entt::poly_call<5>(*this); + } +}; + +template +struct common_members { + static void decr(Type &self) { + self.set(self.get() - 1); + } + + static double mul(const Type &self, double v) { + return v * self.get(); + } +}; + +static int absolutely_random() { + return 42; +} + +template +using common_impl = entt::value_list< + &Type::incr, + &Type::set, + &Type::get, + &common_members::decr, + &common_members::mul, + &absolutely_random>; + +struct Deduced + : entt::type_list<> { + template + using type = common_type; + + template + using members = common_members; + + template + using impl = common_impl; +}; + +struct Defined + : entt::type_list< + void(), + void(int), + int() const, + void(), + int(int) const, + int() const> { + template + using type = common_type; + + template + using members = common_members; + + template + using impl = common_impl; +}; + +struct DeducedEmbedded + : entt::type_list<> { + template + struct type: Base { + int get() const { + return entt::poly_call<0>(*this); + } + }; + + template + using impl = entt::value_list<&Type::get>; +}; + +struct DefinedEmbedded + : entt::type_list { + template + struct type: Base { + // non-const get on purpose + int get() { + return entt::poly_call<0>(*this); + } + }; + + template + using impl = entt::value_list<&Type::get>; +}; + +struct impl { + impl() = default; + + impl(int v) + : value{v} {} + + void incr() { + ++value; + } + + void set(int v) { + value = v; + } + + int get() const { + return value; + } + + int value{}; +}; + +struct alignas(64u) over_aligned: impl {}; + +template +struct Poly: testing::Test { + template + using type = entt::basic_poly; +}; + +template +using PolyDeathTest = Poly; + +using PolyTypes = ::testing::Types; + +TYPED_TEST_SUITE(Poly, PolyTypes, ); +TYPED_TEST_SUITE(PolyDeathTest, PolyTypes, ); + +template +struct PolyEmbedded: testing::Test { + using type = entt::basic_poly; +}; + +using PolyEmbeddedTypes = ::testing::Types; + +TYPED_TEST_SUITE(PolyEmbedded, PolyEmbeddedTypes, ); + +TYPED_TEST(Poly, Functionalities) { + using poly_type = typename TestFixture::template type<>; + + impl instance{}; + + poly_type empty{}; + poly_type in_place{std::in_place_type, 3}; + poly_type alias{std::in_place_type, instance}; + poly_type value{impl{}}; + + ASSERT_FALSE(empty); + ASSERT_TRUE(in_place); + ASSERT_TRUE(alias); + ASSERT_TRUE(value); + + ASSERT_EQ(empty.type(), entt::type_id()); + ASSERT_EQ(in_place.type(), entt::type_id()); + ASSERT_EQ(alias.type(), entt::type_id()); + ASSERT_EQ(value.type(), entt::type_id()); + + ASSERT_EQ(alias.data(), &instance); + ASSERT_EQ(std::as_const(alias).data(), &instance); + + ASSERT_EQ(value->rand(), 42); + + empty = impl{}; + + ASSERT_TRUE(empty); + ASSERT_NE(empty.data(), nullptr); + ASSERT_NE(std::as_const(empty).data(), nullptr); + ASSERT_EQ(empty.type(), entt::type_id()); + ASSERT_EQ(empty->get(), 0); + + empty.template emplace(3); + + ASSERT_TRUE(empty); + ASSERT_EQ(std::as_const(empty)->get(), 3); + + poly_type ref = in_place.as_ref(); + + ASSERT_TRUE(ref); + ASSERT_NE(ref.data(), nullptr); + ASSERT_EQ(ref.data(), in_place.data()); + ASSERT_EQ(std::as_const(ref).data(), std::as_const(in_place).data()); + ASSERT_EQ(ref.type(), entt::type_id()); + ASSERT_EQ(ref->get(), 3); + + poly_type null{}; + std::swap(empty, null); + + ASSERT_FALSE(empty); + + poly_type copy = in_place; + + ASSERT_TRUE(copy); + ASSERT_EQ(copy->get(), 3); + + poly_type move = std::move(copy); + + ASSERT_TRUE(move); + ASSERT_TRUE(copy); + ASSERT_EQ(move->get(), 3); + + move.reset(); + + ASSERT_FALSE(move); + ASSERT_EQ(move.type(), entt::type_id()); +} + +TYPED_TEST(PolyEmbedded, EmbeddedVtable) { + using poly_type = typename TestFixture::type; + + poly_type poly{impl{}}; + auto *ptr = static_cast(poly.data()); + + ASSERT_TRUE(poly); + ASSERT_NE(poly.data(), nullptr); + ASSERT_NE(std::as_const(poly).data(), nullptr); + ASSERT_EQ(poly->get(), 0); + + ptr->value = 2; + + ASSERT_EQ(poly->get(), 2); +} + +TYPED_TEST(Poly, Owned) { + using poly_type = typename TestFixture::template type<>; + + poly_type poly{impl{}}; + auto *ptr = static_cast(poly.data()); + + ASSERT_TRUE(poly); + ASSERT_NE(poly.data(), nullptr); + ASSERT_NE(std::as_const(poly).data(), nullptr); + ASSERT_EQ(ptr->value, 0); + ASSERT_EQ(poly->get(), 0); + + poly->set(1); + poly->incr(); + + ASSERT_EQ(ptr->value, 2); + ASSERT_EQ(std::as_const(poly)->get(), 2); + ASSERT_EQ(poly->mul(3), 6); + + poly->decr(); + + ASSERT_EQ(ptr->value, 1); + ASSERT_EQ(poly->get(), 1); + ASSERT_EQ(poly->mul(3), 3); +} + +TYPED_TEST(Poly, Reference) { + using poly_type = typename TestFixture::template type<>; + + impl instance{}; + poly_type poly{std::in_place_type, instance}; + + ASSERT_TRUE(poly); + ASSERT_NE(poly.data(), nullptr); + ASSERT_NE(std::as_const(poly).data(), nullptr); + ASSERT_EQ(instance.value, 0); + ASSERT_EQ(poly->get(), 0); + + poly->set(1); + poly->incr(); + + ASSERT_EQ(instance.value, 2); + ASSERT_EQ(std::as_const(poly)->get(), 2); + ASSERT_EQ(poly->mul(3), 6); + + poly->decr(); + + ASSERT_EQ(instance.value, 1); + ASSERT_EQ(poly->get(), 1); + ASSERT_EQ(poly->mul(3), 3); +} + +TYPED_TEST(Poly, ConstReference) { + using poly_type = typename TestFixture::template type<>; + + impl instance{}; + poly_type poly{std::in_place_type, instance}; + + ASSERT_TRUE(poly); + ASSERT_EQ(poly.data(), nullptr); + ASSERT_NE(std::as_const(poly).data(), nullptr); + ASSERT_EQ(instance.value, 0); + ASSERT_EQ(poly->get(), 0); + + ASSERT_EQ(instance.value, 0); + ASSERT_EQ(std::as_const(poly)->get(), 0); + ASSERT_EQ(poly->mul(3), 0); + + ASSERT_EQ(instance.value, 0); + ASSERT_EQ(poly->get(), 0); + ASSERT_EQ(poly->mul(3), 0); +} + +TYPED_TEST(PolyDeathTest, ConstReference) { + using poly_type = typename TestFixture::template type<>; + + impl instance{}; + poly_type poly{std::in_place_type, instance}; + + ASSERT_TRUE(poly); + ASSERT_DEATH(poly->set(1), ""); +} + +TYPED_TEST(Poly, AsRef) { + using poly_type = typename TestFixture::template type<>; + + poly_type poly{impl{}}; + auto ref = poly.as_ref(); + auto cref = std::as_const(poly).as_ref(); + + ASSERT_NE(poly.data(), nullptr); + ASSERT_NE(ref.data(), nullptr); + ASSERT_EQ(cref.data(), nullptr); + ASSERT_NE(std::as_const(cref).data(), nullptr); + + std::swap(ref, cref); + + ASSERT_EQ(ref.data(), nullptr); + ASSERT_NE(std::as_const(ref).data(), nullptr); + ASSERT_NE(cref.data(), nullptr); + + ref = ref.as_ref(); + cref = std::as_const(cref).as_ref(); + + ASSERT_EQ(ref.data(), nullptr); + ASSERT_NE(std::as_const(ref).data(), nullptr); + ASSERT_EQ(cref.data(), nullptr); + ASSERT_NE(std::as_const(cref).data(), nullptr); + + ref = impl{}; + cref = impl{}; + + ASSERT_NE(ref.data(), nullptr); + ASSERT_NE(cref.data(), nullptr); +} + +TYPED_TEST(Poly, SBOVsZeroedSBOSize) { + using poly_type = typename TestFixture::template type<>; + using zeroed_type = typename TestFixture::template type<0u>; + + poly_type poly{impl{}}; + const auto broken = poly.data(); + poly_type other = std::move(poly); + + ASSERT_NE(broken, other.data()); + + zeroed_type dyn{impl{}}; + const auto valid = dyn.data(); + zeroed_type same = std::move(dyn); + + ASSERT_EQ(valid, same.data()); + + // everything works as expected + same->incr(); + + ASSERT_EQ(same->get(), 1); +} + +TYPED_TEST(Poly, SboAlignment) { + static constexpr auto alignment = alignof(over_aligned); + typename TestFixture::template type sbo[2]{over_aligned{}, over_aligned{}}; + const auto *data = sbo[0].data(); + + ASSERT_TRUE((reinterpret_cast(sbo[0u].data()) % alignment) == 0u); + ASSERT_TRUE((reinterpret_cast(sbo[1u].data()) % alignment) == 0u); + + std::swap(sbo[0], sbo[1]); + + ASSERT_TRUE((reinterpret_cast(sbo[0u].data()) % alignment) == 0u); + ASSERT_TRUE((reinterpret_cast(sbo[1u].data()) % alignment) == 0u); + + ASSERT_NE(data, sbo[1].data()); +} + +TYPED_TEST(Poly, NoSboAlignment) { + static constexpr auto alignment = alignof(over_aligned); + typename TestFixture::template type nosbo[2]{over_aligned{}, over_aligned{}}; + const auto *data = nosbo[0].data(); + + ASSERT_TRUE((reinterpret_cast(nosbo[0u].data()) % alignment) == 0u); + ASSERT_TRUE((reinterpret_cast(nosbo[1u].data()) % alignment) == 0u); + + std::swap(nosbo[0], nosbo[1]); + + ASSERT_TRUE((reinterpret_cast(nosbo[0u].data()) % alignment) == 0u); + ASSERT_TRUE((reinterpret_cast(nosbo[1u].data()) % alignment) == 0u); + + ASSERT_EQ(data, nosbo[1].data()); +} diff --git a/modules/entt/test/entt/process/process.cpp b/modules/entt/test/entt/process/process.cpp index 4926c42..4c5c657 100644 --- a/modules/entt/test/entt/process/process.cpp +++ b/modules/entt/test/entt/process/process.cpp @@ -1,21 +1,54 @@ -#include #include +#include #include -struct fake_process: entt::process { - using process_type = entt::process; +struct fake_delta {}; + +template +struct fake_process: entt::process, Delta> { + using process_type = entt::process, Delta>; + using delta_type = typename process_type::delta_type; + + fake_process() + : init_invoked{false}, + update_invoked{false}, + succeeded_invoked{false}, + failed_invoked{false}, + aborted_invoked{false} {} + + void succeed() noexcept { + process_type::succeed(); + } + + void fail() noexcept { + process_type::fail(); + } + + void pause() noexcept { + process_type::pause(); + } + + void unpause() noexcept { + process_type::unpause(); + } + + void init() { + init_invoked = true; + } + + void succeeded() { + succeeded_invoked = true; + } - void succeed() noexcept { process_type::succeed(); } - void fail() noexcept { process_type::fail(); } - void pause() noexcept { process_type::pause(); } - void unpause() noexcept { process_type::unpause(); } + void failed() { + failed_invoked = true; + } - void init() { init_invoked = true; } - void succeeded() { succeeded_invoked = true; } - void failed() { failed_invoked = true; } - void aborted() { aborted_invoked = true; } + void aborted() { + aborted_invoked = true; + } - void update(delta_type, void *data) { + void update(typename entt::process, Delta>::delta_type, void *data) { if(data) { (*static_cast(data))++; } @@ -23,19 +56,20 @@ struct fake_process: entt::process { update_invoked = true; } - bool init_invoked{false}; - bool update_invoked{false}; - bool succeeded_invoked{false}; - bool failed_invoked{false}; - bool aborted_invoked{false}; + bool init_invoked; + bool update_invoked; + bool succeeded_invoked; + bool failed_invoked; + bool aborted_invoked; }; TEST(Process, Basics) { - fake_process process; + fake_process process{}; ASSERT_FALSE(process.alive()); - ASSERT_FALSE(process.dead()); + ASSERT_FALSE(process.finished()); ASSERT_FALSE(process.paused()); + ASSERT_FALSE(process.rejected()); process.succeed(); process.fail(); @@ -44,39 +78,58 @@ TEST(Process, Basics) { process.unpause(); ASSERT_FALSE(process.alive()); - ASSERT_FALSE(process.dead()); + ASSERT_FALSE(process.finished()); ASSERT_FALSE(process.paused()); + ASSERT_FALSE(process.rejected()); process.tick(0); ASSERT_TRUE(process.alive()); - ASSERT_FALSE(process.dead()); + ASSERT_FALSE(process.finished()); ASSERT_FALSE(process.paused()); + ASSERT_FALSE(process.rejected()); process.pause(); ASSERT_TRUE(process.alive()); - ASSERT_FALSE(process.dead()); + ASSERT_FALSE(process.finished()); ASSERT_TRUE(process.paused()); + ASSERT_FALSE(process.rejected()); process.unpause(); ASSERT_TRUE(process.alive()); - ASSERT_FALSE(process.dead()); + ASSERT_FALSE(process.finished()); ASSERT_FALSE(process.paused()); + ASSERT_FALSE(process.rejected()); + + process.fail(); + + ASSERT_FALSE(process.alive()); + ASSERT_FALSE(process.finished()); + ASSERT_FALSE(process.paused()); + ASSERT_FALSE(process.rejected()); + + process.tick(0); + + ASSERT_FALSE(process.alive()); + ASSERT_FALSE(process.finished()); + ASSERT_FALSE(process.paused()); + ASSERT_TRUE(process.rejected()); } TEST(Process, Succeeded) { - fake_process process; + fake_process process{}; - process.tick(0); - process.tick(0); + process.tick({}); + process.tick({}); process.succeed(); - process.tick(0); + process.tick({}); ASSERT_FALSE(process.alive()); - ASSERT_TRUE(process.dead()); + ASSERT_TRUE(process.finished()); ASSERT_FALSE(process.paused()); + ASSERT_FALSE(process.rejected()); ASSERT_TRUE(process.init_invoked); ASSERT_TRUE(process.update_invoked); @@ -86,7 +139,7 @@ TEST(Process, Succeeded) { } TEST(Process, Fail) { - fake_process process; + fake_process process{}; process.tick(0); process.tick(0); @@ -94,8 +147,9 @@ TEST(Process, Fail) { process.tick(0); ASSERT_FALSE(process.alive()); - ASSERT_TRUE(process.dead()); + ASSERT_FALSE(process.finished()); ASSERT_FALSE(process.paused()); + ASSERT_TRUE(process.rejected()); ASSERT_TRUE(process.init_invoked); ASSERT_TRUE(process.update_invoked); @@ -105,17 +159,18 @@ TEST(Process, Fail) { } TEST(Process, Data) { - fake_process process; + fake_process process{}; int value = 0; - process.tick(0); - process.tick(0, &value); + process.tick({}); + process.tick({}, &value); process.succeed(); - process.tick(0, &value); + process.tick({}, &value); ASSERT_FALSE(process.alive()); - ASSERT_TRUE(process.dead()); + ASSERT_TRUE(process.finished()); ASSERT_FALSE(process.paused()); + ASSERT_FALSE(process.rejected()); ASSERT_EQ(value, 1); ASSERT_TRUE(process.init_invoked); @@ -126,15 +181,16 @@ TEST(Process, Data) { } TEST(Process, AbortNextTick) { - fake_process process; + fake_process process{}; process.tick(0); process.abort(); process.tick(0); ASSERT_FALSE(process.alive()); - ASSERT_TRUE(process.dead()); + ASSERT_FALSE(process.finished()); ASSERT_FALSE(process.paused()); + ASSERT_TRUE(process.rejected()); ASSERT_TRUE(process.init_invoked); ASSERT_FALSE(process.update_invoked); @@ -144,14 +200,15 @@ TEST(Process, AbortNextTick) { } TEST(Process, AbortImmediately) { - fake_process process; + fake_process process{}; - process.tick(0); + process.tick({}); process.abort(true); ASSERT_FALSE(process.alive()); - ASSERT_TRUE(process.dead()); + ASSERT_FALSE(process.finished()); ASSERT_FALSE(process.paused()); + ASSERT_TRUE(process.rejected()); ASSERT_TRUE(process.init_invoked); ASSERT_FALSE(process.update_invoked); @@ -173,7 +230,7 @@ TEST(ProcessAdaptor, Resolved) { process.tick(0); process.tick(0); - ASSERT_TRUE(process.dead()); + ASSERT_TRUE(process.finished()); ASSERT_TRUE(updated); } @@ -207,6 +264,6 @@ TEST(ProcessAdaptor, Data) { process.tick(0); process.tick(0, &value); - ASSERT_TRUE(process.dead()); + ASSERT_TRUE(process.finished()); ASSERT_EQ(value, 42); } diff --git a/modules/entt/test/entt/process/scheduler.cpp b/modules/entt/test/entt/process/scheduler.cpp index 003aef5..c08c44c 100644 --- a/modules/entt/test/entt/process/scheduler.cpp +++ b/modules/entt/test/entt/process/scheduler.cpp @@ -1,15 +1,20 @@ #include +#include #include -#include #include +#include struct foo_process: entt::process { foo_process(std::function upd, std::function abort) - : on_update{upd}, on_aborted{abort} - {} + : on_update{upd}, on_aborted{abort} {} - void update(delta_type, void *) { on_update(); } - void aborted() { on_aborted(); } + void update(delta_type, void *) { + on_update(); + } + + void aborted() { + on_aborted(); + } std::function on_update; std::function on_aborted; @@ -17,43 +22,43 @@ struct foo_process: entt::process { struct succeeded_process: entt::process { void update(delta_type, void *) { - ASSERT_FALSE(updated); - updated = true; ++invoked; succeed(); } - static unsigned int invoked; - bool updated = false; + static inline unsigned int invoked; }; -unsigned int succeeded_process::invoked = 0; - struct failed_process: entt::process { void update(delta_type, void *) { - ASSERT_FALSE(updated); - updated = true; + ++invoked; fail(); } - bool updated = false; + static inline unsigned int invoked; +}; + +struct Scheduler: ::testing::Test { + void SetUp() override { + succeeded_process::invoked = 0u; + failed_process::invoked = 0u; + } }; -TEST(Scheduler, Functionalities) { +TEST_F(Scheduler, Functionalities) { entt::scheduler scheduler{}; bool updated = false; bool aborted = false; - ASSERT_EQ(scheduler.size(), entt::scheduler::size_type{}); + ASSERT_EQ(scheduler.size(), 0u); ASSERT_TRUE(scheduler.empty()); scheduler.attach( - [&updated](){ updated = true; }, - [&aborted](){ aborted = true; } - ); + [&updated]() { updated = true; }, + [&aborted]() { aborted = true; }); - ASSERT_NE(scheduler.size(), entt::scheduler::size_type{}); + ASSERT_NE(scheduler.size(), 0u); ASSERT_FALSE(scheduler.empty()); scheduler.update(0); @@ -62,52 +67,88 @@ TEST(Scheduler, Functionalities) { ASSERT_TRUE(updated); ASSERT_TRUE(aborted); - ASSERT_NE(scheduler.size(), entt::scheduler::size_type{}); + ASSERT_NE(scheduler.size(), 0u); ASSERT_FALSE(scheduler.empty()); scheduler.clear(); - ASSERT_EQ(scheduler.size(), entt::scheduler::size_type{}); + ASSERT_EQ(scheduler.size(), 0u); ASSERT_TRUE(scheduler.empty()); } -TEST(Scheduler, Then) { +TEST_F(Scheduler, Then) { entt::scheduler scheduler; + // failing process with successor + scheduler.attach() + .then() + .then() + .then(); + + // failing process without successor + scheduler.attach() + .then() + .then(); + + // non-failing process scheduler.attach() - .then() - .then() - .then(); + .then(); - for(auto i = 0; i < 8; ++i) { + ASSERT_EQ(succeeded_process::invoked, 0u); + ASSERT_EQ(failed_process::invoked, 0u); + + while(!scheduler.empty()) { scheduler.update(0); } - ASSERT_EQ(succeeded_process::invoked, 2u); + ASSERT_EQ(succeeded_process::invoked, 6u); + ASSERT_EQ(failed_process::invoked, 2u); } -TEST(Scheduler, Functor) { +TEST_F(Scheduler, Functor) { entt::scheduler scheduler; bool first_functor = false; bool second_functor = false; - scheduler.attach([&first_functor](auto, void *, auto resolve, auto){ + auto attach = [&first_functor](auto, void *, auto resolve, auto) { ASSERT_FALSE(first_functor); first_functor = true; resolve(); - }).then([&second_functor](auto, void *, auto, auto reject){ + }; + + auto then = [&second_functor](auto, void *, auto, auto reject) { ASSERT_FALSE(second_functor); second_functor = true; reject(); - }).then([](auto...){ - FAIL(); - }); + }; + + scheduler.attach(std::move(attach)).then(std::move(then)).then([](auto...) { FAIL(); }); - for(auto i = 0; i < 8; ++i) { + while(!scheduler.empty()) { scheduler.update(0); } ASSERT_TRUE(first_functor); ASSERT_TRUE(second_functor); + ASSERT_TRUE(scheduler.empty()); +} + +TEST_F(Scheduler, SpawningProcess) { + entt::scheduler scheduler; + + scheduler.attach([&scheduler](auto, void *, auto resolve, auto) { + scheduler.attach().then(); + resolve(); + }); + + ASSERT_EQ(succeeded_process::invoked, 0u); + ASSERT_EQ(failed_process::invoked, 0u); + + while(!scheduler.empty()) { + scheduler.update(0); + } + + ASSERT_EQ(succeeded_process::invoked, 1u); + ASSERT_EQ(failed_process::invoked, 1u); } diff --git a/modules/entt/test/entt/resource/resource.cpp b/modules/entt/test/entt/resource/resource.cpp index ee26b7d..78e207a 100644 --- a/modules/entt/test/entt/resource/resource.cpp +++ b/modules/entt/test/entt/resource/resource.cpp @@ -1,109 +1,144 @@ -#include #include -#include +#include +#include -struct resource { int value; }; +struct base { + virtual ~base() = default; -struct loader: entt::resource_loader { - std::shared_ptr load(int value) const { - return std::shared_ptr(new resource{ value }); + virtual const entt::type_info &type() const ENTT_NOEXCEPT { + return entt::type_id(); } }; -struct broken_loader: entt::resource_loader { - std::shared_ptr load(int) const { - return nullptr; +struct derived: base { + const entt::type_info &type() const ENTT_NOEXCEPT override { + return entt::type_id(); } }; +template +entt::resource dynamic_resource_cast(const entt::resource &other) { + if(other->type() == entt::type_id()) { + return entt::resource{other, static_cast(*other)}; + } + + return {}; +} + TEST(Resource, Functionalities) { - entt::resource_cache cache; + entt::resource resource{}; - constexpr auto hs1 = entt::hashed_string{"res1"}; - constexpr auto hs2 = entt::hashed_string{"res2"}; + ASSERT_FALSE(resource); + ASSERT_EQ(resource.operator->(), nullptr); + ASSERT_EQ(resource.use_count(), 0l); - ASSERT_EQ(cache.size(), entt::resource_cache::size_type{}); - ASSERT_TRUE(cache.empty()); - ASSERT_FALSE(cache.contains(hs1)); - ASSERT_FALSE(cache.contains(hs2)); + const auto value = std::make_shared(); + entt::resource other{value}; - ASSERT_FALSE(cache.load(hs1, 42)); - ASSERT_FALSE(cache.reload(hs1, 42)); + ASSERT_TRUE(other); + ASSERT_EQ(other.operator->(), value.get()); + ASSERT_EQ(&static_cast(other), value.get()); + ASSERT_EQ(&*other, value.get()); + ASSERT_EQ(other.use_count(), 2l); - ASSERT_EQ(cache.size(), entt::resource_cache::size_type{}); - ASSERT_TRUE(cache.empty()); - ASSERT_FALSE(cache.contains(hs1)); - ASSERT_FALSE(cache.contains(hs2)); + entt::resource copy{resource}; + entt::resource move{std::move(other)}; - ASSERT_TRUE(cache.load(hs1, 42)); - ASSERT_TRUE(cache.reload(hs1, 42)); + ASSERT_FALSE(copy); + ASSERT_TRUE(move); + + copy = std::move(move); + move = copy; + + ASSERT_TRUE(copy); + ASSERT_TRUE(move); + ASSERT_EQ(copy, move); +} - ASSERT_NE(cache.size(), entt::resource_cache::size_type{}); - ASSERT_FALSE(cache.empty()); - ASSERT_TRUE(cache.contains(hs1)); - ASSERT_FALSE(cache.contains(hs2)); - ASSERT_EQ((*cache.handle(hs1)).value, 42); +TEST(Resource, DerivedToBase) { + entt::resource resource{std::make_shared()}; + entt::resource other{resource}; + entt::resource cother{resource}; - ASSERT_TRUE(cache.load(hs1, 42)); - ASSERT_TRUE(cache.load(hs2, 42)); + ASSERT_TRUE(resource); + ASSERT_TRUE(other); + ASSERT_TRUE(cother); + ASSERT_EQ(resource, other); + ASSERT_EQ(other, cother); - ASSERT_NE(cache.size(), entt::resource_cache::size_type{}); - ASSERT_FALSE(cache.empty()); - ASSERT_TRUE(cache.contains(hs1)); - ASSERT_TRUE(cache.contains(hs2)); - ASSERT_EQ((*cache.handle(hs1)).value, 42); - ASSERT_EQ(cache.handle(hs2)->value, 42); + other = resource; + cother = resource; - ASSERT_NO_THROW(cache.discard(hs1)); + ASSERT_EQ(resource, other); + ASSERT_EQ(other, cother); +} - ASSERT_FALSE(cache.contains(hs1)); - ASSERT_TRUE(cache.contains(hs2)); - ASSERT_EQ(cache.handle(hs2)->value, 42); +TEST(Resource, ConstNonConstAndAllInBetween) { + entt::resource resource{std::make_shared()}; + entt::resource other{resource}; - ASSERT_TRUE(cache.load(hs1, 42)); - ASSERT_NO_THROW(cache.clear()); + static_assert(std::is_same_v); + static_assert(std::is_same_v{other}), const derived &>); + static_assert(std::is_same_v); - ASSERT_EQ(cache.size(), entt::resource_cache::size_type{}); - ASSERT_TRUE(cache.empty()); - ASSERT_FALSE(cache.contains(hs1)); - ASSERT_FALSE(cache.contains(hs2)); + entt::resource copy{resource}; + entt::resource move{std::move(other)}; - ASSERT_TRUE(cache.load(hs1, 42)); + ASSERT_TRUE(resource); + ASSERT_FALSE(other); - ASSERT_NE(cache.size(), entt::resource_cache::size_type{}); - ASSERT_FALSE(cache.empty()); - ASSERT_TRUE(cache.handle(hs1)); - ASSERT_FALSE(cache.handle(hs2)); + ASSERT_TRUE(copy); + ASSERT_EQ(copy, resource); + ASSERT_NE(copy, entt::resource{}); + ASSERT_EQ(copy.use_count(), 3u); - ASSERT_TRUE(cache.handle(hs1)); - ASSERT_EQ(&cache.handle(hs1).get(), &static_cast(cache.handle(hs1))); - ASSERT_NO_THROW(cache.clear()); + ASSERT_TRUE(move); + ASSERT_EQ(move, resource); + ASSERT_NE(move, entt::resource{}); + ASSERT_EQ(move.use_count(), 3u); - ASSERT_EQ(cache.size(), entt::resource_cache::size_type{}); - ASSERT_TRUE(cache.empty()); + copy = resource; + move = std::move(resource); - ASSERT_TRUE(cache.temp(42)); - ASSERT_TRUE(cache.empty()); + ASSERT_FALSE(resource); + ASSERT_FALSE(other); - ASSERT_FALSE(entt::resource_handle{}); - ASSERT_TRUE(std::is_copy_constructible_v>); - ASSERT_TRUE(std::is_move_constructible_v>); - ASSERT_TRUE(std::is_copy_assignable_v>); - ASSERT_TRUE(std::is_move_assignable_v>); + ASSERT_TRUE(copy); + ASSERT_TRUE(move); + ASSERT_EQ(copy.use_count(), 2u); } -TEST(Resource, MutableHandle) { - entt::resource_cache cache; +TEST(Resource, DynamicResourceHandleCast) { + entt::resource resource{std::make_shared()}; + entt::resource other = resource; + + ASSERT_TRUE(other); + ASSERT_EQ(resource.use_count(), 2u); + ASSERT_EQ(resource, other); + + entt::resource cast = dynamic_resource_cast(other); + + ASSERT_TRUE(cast); + ASSERT_EQ(resource.use_count(), 3u); + ASSERT_EQ(resource, cast); + + other = entt::resource{std::make_shared()}; + cast = dynamic_resource_cast(other); + + ASSERT_FALSE(cast); + ASSERT_EQ(resource.use_count(), 1u); +} - constexpr auto hs = entt::hashed_string{"res"}; - auto handle = cache.load(hs, 0); +TEST(Resource, Comparison) { + entt::resource resource{std::make_shared()}; + entt::resource other = resource; - ASSERT_TRUE(handle); + ASSERT_TRUE(resource == other); + ASSERT_FALSE(resource != other); - ++handle.get().value; - ++static_cast(handle).value; - ++(*handle).value; - ++handle->value; + ASSERT_FALSE(resource < other); + ASSERT_FALSE(resource > other); - ASSERT_EQ(cache.handle(hs)->value, 4); + ASSERT_TRUE(resource <= other); + ASSERT_TRUE(resource >= other); } diff --git a/modules/entt/test/entt/resource/resource_cache.cpp b/modules/entt/test/entt/resource/resource_cache.cpp new file mode 100644 index 0000000..b9645cc --- /dev/null +++ b/modules/entt/test/entt/resource/resource_cache.cpp @@ -0,0 +1,428 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "../common/throwing_allocator.hpp" + +struct broken_tag {}; +struct with_callback {}; + +template +struct loader { + using result_type = std::shared_ptr; + + template + result_type operator()(Args &&...args) const { + return std::make_shared(std::forward(args)...); + } + + template + result_type operator()(with_callback, Func &&func) const { + return std::forward(func)(); + } + + template + result_type operator()(broken_tag, Args &&...) const { + return {}; + } +}; + +TEST(ResourceCache, Functionalities) { + using namespace entt::literals; + + entt::resource_cache cache; + + ASSERT_NO_THROW([[maybe_unused]] auto alloc = cache.get_allocator()); + + ASSERT_TRUE(cache.empty()); + ASSERT_EQ(cache.size(), 0u); + + ASSERT_EQ(cache.begin(), cache.end()); + ASSERT_EQ(std::as_const(cache).begin(), std::as_const(cache).end()); + ASSERT_EQ(cache.cbegin(), cache.cend()); + + ASSERT_FALSE(cache.contains("resource"_hs)); + + cache.load("resource"_hs, 42); + + ASSERT_FALSE(cache.empty()); + ASSERT_EQ(cache.size(), 1u); + + ASSERT_NE(cache.begin(), cache.end()); + ASSERT_NE(std::as_const(cache).begin(), std::as_const(cache).end()); + ASSERT_NE(cache.cbegin(), cache.cend()); + + ASSERT_TRUE(cache.contains("resource"_hs)); + + cache.clear(); + + ASSERT_TRUE(cache.empty()); + ASSERT_EQ(cache.size(), 0u); + + ASSERT_EQ(cache.begin(), cache.end()); + ASSERT_EQ(std::as_const(cache).begin(), std::as_const(cache).end()); + ASSERT_EQ(cache.cbegin(), cache.cend()); + + ASSERT_FALSE(cache.contains("resource"_hs)); +} + +TEST(ResourceCache, Constructors) { + using namespace entt::literals; + + entt::resource_cache cache; + + cache = entt::resource_cache{std::allocator{}}; + cache = entt::resource_cache{entt::resource_loader{}, std::allocator{}}; + + cache.load("resource"_hs, 42u); + + entt::resource_cache temp{cache, cache.get_allocator()}; + entt::resource_cache other{std::move(temp), cache.get_allocator()}; + + ASSERT_EQ(cache.size(), 1u); + ASSERT_EQ(other.size(), 1u); +} + +TEST(ResourceCache, Copy) { + using namespace entt::literals; + + entt::resource_cache cache; + cache.load("resource"_hs, 42u); + + entt::resource_cache other{cache}; + + ASSERT_TRUE(cache.contains("resource"_hs)); + ASSERT_TRUE(other.contains("resource"_hs)); + + cache.load("foo"_hs, 99u); + cache.load("bar"_hs, 77u); + other.load("quux"_hs, 0u); + other = cache; + + ASSERT_TRUE(other.contains("resource"_hs)); + ASSERT_TRUE(other.contains("foo"_hs)); + ASSERT_TRUE(other.contains("bar"_hs)); + ASSERT_FALSE(other.contains("quux"_hs)); + + ASSERT_EQ(other["resource"_hs], 42u); + ASSERT_EQ(other["foo"_hs], 99u); + ASSERT_EQ(other["bar"_hs], 77u); +} + +TEST(ResourceCache, Move) { + using namespace entt::literals; + + entt::resource_cache cache; + cache.load("resource"_hs, 42u); + + entt::resource_cache other{std::move(cache)}; + + ASSERT_EQ(cache.size(), 0u); + ASSERT_TRUE(other.contains("resource"_hs)); + + cache = other; + cache.load("foo"_hs, 99u); + cache.load("bar"_hs, 77u); + other.load("quux"_hs, 0u); + other = std::move(cache); + + ASSERT_EQ(cache.size(), 0u); + ASSERT_TRUE(other.contains("resource"_hs)); + ASSERT_TRUE(other.contains("foo"_hs)); + ASSERT_TRUE(other.contains("bar"_hs)); + ASSERT_FALSE(other.contains("quux"_hs)); + + ASSERT_EQ(other["resource"_hs], 42u); + ASSERT_EQ(other["foo"_hs], 99u); + ASSERT_EQ(other["bar"_hs], 77u); +} + +TEST(ResourceCache, Iterator) { + using namespace entt::literals; + + using iterator = typename entt::resource_cache::iterator; + + static_assert(std::is_same_v>>); + static_assert(std::is_same_v>>>); + static_assert(std::is_same_v>>); + + entt::resource_cache cache; + cache.load("resource"_hs, 42); + + iterator end{cache.begin()}; + iterator begin{}; + begin = cache.end(); + std::swap(begin, end); + + ASSERT_EQ(begin, cache.begin()); + ASSERT_EQ(end, cache.end()); + ASSERT_NE(begin, end); + + ASSERT_EQ(begin++, cache.begin()); + ASSERT_EQ(begin--, cache.end()); + + ASSERT_EQ(begin + 1, cache.end()); + ASSERT_EQ(end - 1, cache.begin()); + + ASSERT_EQ(++begin, cache.end()); + ASSERT_EQ(--begin, cache.begin()); + + ASSERT_EQ(begin += 1, cache.end()); + ASSERT_EQ(begin -= 1, cache.begin()); + + ASSERT_EQ(begin + (end - begin), cache.end()); + ASSERT_EQ(begin - (begin - end), cache.end()); + + ASSERT_EQ(end - (end - begin), cache.begin()); + ASSERT_EQ(end + (begin - end), cache.begin()); + + ASSERT_EQ(begin[0u].first, cache.begin()->first); + ASSERT_EQ(begin[0u].second, (*cache.begin()).second); + + ASSERT_LT(begin, end); + ASSERT_LE(begin, cache.begin()); + + ASSERT_GT(end, begin); + ASSERT_GE(end, cache.end()); + + cache.load("other"_hs, 3); + begin = cache.begin(); + + ASSERT_EQ(begin[0u].first, "resource"_hs); + ASSERT_EQ(begin[1u].second, 3); +} + +TEST(ResourceCache, ConstIterator) { + using namespace entt::literals; + + using iterator = typename entt::resource_cache::const_iterator; + + static_assert(std::is_same_v>>); + static_assert(std::is_same_v>>>); + static_assert(std::is_same_v>>); + + entt::resource_cache cache; + cache.load("resource"_hs, 42); + + iterator cend{cache.cbegin()}; + iterator cbegin{}; + cbegin = cache.cend(); + std::swap(cbegin, cend); + + ASSERT_EQ(cbegin, cache.cbegin()); + ASSERT_EQ(cend, cache.cend()); + ASSERT_NE(cbegin, cend); + + ASSERT_EQ(cbegin++, cache.cbegin()); + ASSERT_EQ(cbegin--, cache.cend()); + + ASSERT_EQ(cbegin + 1, cache.cend()); + ASSERT_EQ(cend - 1, cache.cbegin()); + + ASSERT_EQ(++cbegin, cache.cend()); + ASSERT_EQ(--cbegin, cache.cbegin()); + + ASSERT_EQ(cbegin += 1, cache.cend()); + ASSERT_EQ(cbegin -= 1, cache.cbegin()); + + ASSERT_EQ(cbegin + (cend - cbegin), cache.cend()); + ASSERT_EQ(cbegin - (cbegin - cend), cache.cend()); + + ASSERT_EQ(cend - (cend - cbegin), cache.cbegin()); + ASSERT_EQ(cend + (cbegin - cend), cache.cbegin()); + + ASSERT_EQ(cbegin[0u].first, cache.cbegin()->first); + ASSERT_EQ(cbegin[0u].second, (*cache.cbegin()).second); + + ASSERT_LT(cbegin, cend); + ASSERT_LE(cbegin, cache.cbegin()); + + ASSERT_GT(cend, cbegin); + ASSERT_GE(cend, cache.cend()); + + cache.load("other"_hs, 3); + cbegin = cache.cbegin(); + + ASSERT_EQ(cbegin[0u].first, "resource"_hs); + ASSERT_EQ(cbegin[1u].second, 3); +} + +TEST(ResourceCache, IteratorConversion) { + using namespace entt::literals; + + entt::resource_cache cache; + cache.load("resource"_hs, 42); + + typename entt::resource_cache::iterator it = cache.begin(); + typename entt::resource_cache::const_iterator cit = it; + + static_assert(std::is_same_v>>); + static_assert(std::is_same_v>>); + + ASSERT_EQ(it->first, "resource"_hs); + ASSERT_EQ((*it).second, 42); + ASSERT_EQ(it->first, cit->first); + ASSERT_EQ((*it).second, (*cit).second); + + ASSERT_EQ(it - cit, 0); + ASSERT_EQ(cit - it, 0); + ASSERT_LE(it, cit); + ASSERT_LE(cit, it); + ASSERT_GE(it, cit); + ASSERT_GE(cit, it); + ASSERT_EQ(it, cit); + ASSERT_NE(++cit, it); +} + +TEST(ResourceCache, Load) { + using namespace entt::literals; + + entt::resource_cache cache; + typename entt::resource_cache::iterator it; + bool result; + + ASSERT_TRUE(cache.empty()); + ASSERT_EQ(cache.size(), 0u); + ASSERT_EQ(cache["resource"_hs], entt::resource{}); + ASSERT_EQ(std::as_const(cache)["resource"_hs], entt::resource{}); + ASSERT_FALSE(cache.contains("resource"_hs)); + + std::tie(it, result) = cache.load("resource"_hs, 1); + + ASSERT_TRUE(result); + ASSERT_EQ(cache.size(), 1u); + ASSERT_EQ(it, --cache.end()); + ASSERT_TRUE(cache.contains("resource"_hs)); + ASSERT_NE(cache["resource"_hs], entt::resource{}); + ASSERT_NE(std::as_const(cache)["resource"_hs], entt::resource{}); + ASSERT_EQ(it->first, "resource"_hs); + ASSERT_EQ(it->second, 1); + + std::tie(it, result) = cache.load("resource"_hs, 2); + + ASSERT_FALSE(result); + ASSERT_EQ(cache.size(), 1u); + ASSERT_EQ(it, --cache.end()); + ASSERT_EQ(it->second, 1); + + std::tie(it, result) = cache.force_load("resource"_hs, 3); + + ASSERT_TRUE(result); + ASSERT_EQ(cache.size(), 1u); + ASSERT_EQ(it, --cache.end()); + ASSERT_EQ(it->second, 3); +} + +TEST(ResourceCache, Erase) { + static constexpr std::size_t resource_count = 8u; + entt::resource_cache cache; + + for(std::size_t next{}, last = resource_count + 1u; next < last; ++next) { + cache.load(next, next); + } + + ASSERT_EQ(cache.size(), resource_count + 1u); + + for(std::size_t next{}, last = resource_count + 1u; next < last; ++next) { + ASSERT_TRUE(cache.contains(next)); + } + + auto it = cache.erase(++cache.begin()); + it = cache.erase(it, it + 1); + + ASSERT_EQ((--cache.end())->first, 6u); + ASSERT_EQ(cache.erase(6u), 1u); + ASSERT_EQ(cache.erase(6u), 0u); + + ASSERT_EQ(cache.size(), resource_count + 1u - 3u); + + ASSERT_EQ(it, ++cache.begin()); + ASSERT_EQ(it->first, 7u); + ASSERT_EQ((--cache.end())->first, 5u); + + for(std::size_t next{}, last = resource_count + 1u; next < last; ++next) { + if(next == 1u || next == 8u || next == 6u) { + ASSERT_FALSE(cache.contains(next)); + } else { + ASSERT_TRUE(cache.contains(next)); + } + } + + cache.erase(cache.begin(), cache.end()); + + for(std::size_t next{}, last = resource_count + 1u; next < last; ++next) { + ASSERT_FALSE(cache.contains(next)); + } + + ASSERT_EQ(cache.size(), 0u); +} + +TEST(ResourceCache, Indexing) { + using namespace entt::literals; + + entt::resource_cache cache; + + ASSERT_FALSE(cache.contains("resource"_hs)); + ASSERT_FALSE(cache["resource"_hs]); + ASSERT_FALSE(std::as_const(cache)["resource"_hs]); + + cache.load("resource"_hs, 99); + + ASSERT_TRUE(cache.contains("resource"_hs)); + ASSERT_EQ(cache[std::move("resource"_hs)], 99); + ASSERT_EQ(std::as_const(cache)["resource"_hs], 99); +} + +TEST(ResourceCache, LoaderDispatching) { + using namespace entt::literals; + + entt::resource_cache> cache; + cache.force_load("resource"_hs, 99); + + ASSERT_TRUE(cache.contains("resource"_hs)); + ASSERT_EQ(cache["resource"_hs], 99); + + cache.force_load("resource"_hs, with_callback{}, []() { return std::make_shared(42); }); + + ASSERT_TRUE(cache.contains("resource"_hs)); + ASSERT_EQ(cache["resource"_hs], 42); +} + +TEST(ResourceCache, BrokenLoader) { + using namespace entt::literals; + + entt::resource_cache> cache; + cache.load("resource"_hs, broken_tag{}); + + ASSERT_TRUE(cache.contains("resource"_hs)); + ASSERT_FALSE(cache["resource"_hs]); + + cache.force_load("resource"_hs, 42); + + ASSERT_TRUE(cache.contains("resource"_hs)); + ASSERT_TRUE(cache["resource"_hs]); +} + +TEST(ResourceCache, ThrowingAllocator) { + using namespace entt::literals; + + using allocator = test::throwing_allocator; + using packed_allocator = test::throwing_allocator>>; + using packed_exception = typename packed_allocator::exception_type; + + entt::resource_cache, allocator> cache{}; + + packed_allocator::trigger_on_allocate = true; + + ASSERT_THROW(cache.load("resource"_hs), packed_exception); + ASSERT_FALSE(cache.contains("resource"_hs)); + + packed_allocator::trigger_on_allocate = true; + + ASSERT_THROW(cache.force_load("resource"_hs), packed_exception); + ASSERT_FALSE(cache.contains("resource"_hs)); +} diff --git a/modules/entt/test/entt/resource/resource_loader.cpp b/modules/entt/test/entt/resource/resource_loader.cpp new file mode 100644 index 0000000..80b2119 --- /dev/null +++ b/modules/entt/test/entt/resource/resource_loader.cpp @@ -0,0 +1,13 @@ +#include +#include +#include + +TEST(ResourceLoader, Functionalities) { + using loader_type = entt::resource_loader; + const auto resource = loader_type{}(42); + + static_assert(std::is_same_v>); + + ASSERT_TRUE(resource); + ASSERT_EQ(*resource, 42); +} diff --git a/modules/entt/test/entt/signal/delegate.cpp b/modules/entt/test/entt/signal/delegate.cpp index 49ec432..f41eba5 100644 --- a/modules/entt/test/entt/signal/delegate.cpp +++ b/modules/entt/test/entt/signal/delegate.cpp @@ -1,17 +1,32 @@ +#include +#include +#include #include #include int delegate_function(const int &i) { - return i*i; + return i * i; } -int curried_function(const int *i, int j) { - return *i+j; +int curried_by_ref(const int &i, int j) { + return i + j; +} + +int curried_by_ptr(const int *i, int j) { + return (*i) * j; +} + +int non_const_reference(int &i) { + return i *= i; +} + +int move_only_type(std::unique_ptr ptr) { + return *ptr; } struct delegate_functor { int operator()(int i) { - return i+i; + return i + i; } int identity(int i) const { @@ -23,16 +38,31 @@ struct delegate_functor { }; struct const_nonconst_noexcept { - void f() { ++cnt; } - void g() noexcept { ++cnt; } - void h() const { ++cnt; } - void i() const noexcept { ++cnt; } + void f() { + ++cnt; + } + + void g() noexcept { + ++cnt; + } + + void h() const { + ++cnt; + } + + void i() const noexcept { + ++cnt; + } + + int u{}; + const int v{}; mutable int cnt{0}; }; TEST(Delegate, Functionalities) { entt::delegate ff_del; entt::delegate mf_del; + entt::delegate lf_del; delegate_functor functor; ASSERT_FALSE(ff_del); @@ -40,38 +70,57 @@ TEST(Delegate, Functionalities) { ASSERT_EQ(ff_del, mf_del); ff_del.connect<&delegate_function>(); - mf_del.connect<&delegate_functor::operator()>(&functor); + mf_del.connect<&delegate_functor::operator()>(functor); + lf_del.connect([](const void *ptr, int value) { return static_cast(ptr)->identity(value); }, &functor); ASSERT_TRUE(ff_del); ASSERT_TRUE(mf_del); + ASSERT_TRUE(lf_del); ASSERT_EQ(ff_del(3), 9); ASSERT_EQ(mf_del(3), 6); + ASSERT_EQ(lf_del(3), 3); ff_del.reset(); ASSERT_FALSE(ff_del); - ASSERT_TRUE(mf_del); ASSERT_EQ(ff_del, entt::delegate{}); ASSERT_NE(mf_del, entt::delegate{}); + ASSERT_NE(lf_del, entt::delegate{}); + ASSERT_NE(ff_del, mf_del); + ASSERT_NE(ff_del, lf_del); + ASSERT_NE(mf_del, lf_del); mf_del.reset(); ASSERT_FALSE(ff_del); ASSERT_FALSE(mf_del); + ASSERT_TRUE(lf_del); ASSERT_EQ(ff_del, entt::delegate{}); ASSERT_EQ(mf_del, entt::delegate{}); + ASSERT_NE(lf_del, entt::delegate{}); + ASSERT_EQ(ff_del, mf_del); + ASSERT_NE(ff_del, lf_del); + ASSERT_NE(mf_del, lf_del); +} + +TEST(DelegateDeathTest, InvokeEmpty) { + entt::delegate del; + + ASSERT_FALSE(del); + ASSERT_DEATH(del(42), ""); + ASSERT_DEATH(std::as_const(del)(42), ""); } TEST(Delegate, DataMembers) { entt::delegate delegate; delegate_functor functor; - delegate.connect<&delegate_functor::data_member>(&functor); + delegate.connect<&delegate_functor::data_member>(functor); ASSERT_EQ(delegate(), 42); } @@ -102,42 +151,70 @@ TEST(Delegate, Comparison) { ASSERT_TRUE(lhs == rhs); ASSERT_EQ(lhs, rhs); - lhs.connect<&curried_function>(&value); + lhs.connect<&curried_by_ref>(value); - ASSERT_EQ(lhs, (entt::delegate{entt::connect_arg<&curried_function>, &value})); + ASSERT_EQ(lhs, (entt::delegate{entt::connect_arg<&curried_by_ref>, value})); ASSERT_TRUE(lhs != rhs); ASSERT_FALSE(lhs == rhs); ASSERT_NE(lhs, rhs); - rhs.connect<&curried_function>(&value); + rhs.connect<&curried_by_ref>(value); - ASSERT_EQ(rhs, (entt::delegate{entt::connect_arg<&curried_function>, &value})); + ASSERT_EQ(rhs, (entt::delegate{entt::connect_arg<&curried_by_ref>, value})); ASSERT_FALSE(lhs != rhs); ASSERT_TRUE(lhs == rhs); ASSERT_EQ(lhs, rhs); - lhs.connect<&delegate_functor::operator()>(&functor); + lhs.connect<&curried_by_ptr>(&value); - ASSERT_EQ(lhs, (entt::delegate{entt::connect_arg<&delegate_functor::operator()>, &functor})); + ASSERT_EQ(lhs, (entt::delegate{entt::connect_arg<&curried_by_ptr>, &value})); ASSERT_TRUE(lhs != rhs); ASSERT_FALSE(lhs == rhs); ASSERT_NE(lhs, rhs); - rhs.connect<&delegate_functor::operator()>(&functor); + rhs.connect<&curried_by_ptr>(&value); - ASSERT_EQ(rhs, (entt::delegate{entt::connect_arg<&delegate_functor::operator()>, &functor})); + ASSERT_EQ(rhs, (entt::delegate{entt::connect_arg<&curried_by_ptr>, &value})); ASSERT_FALSE(lhs != rhs); ASSERT_TRUE(lhs == rhs); ASSERT_EQ(lhs, rhs); - lhs.connect<&delegate_functor::operator()>(&other); + lhs.connect<&delegate_functor::operator()>(functor); - ASSERT_EQ(lhs, (entt::delegate{entt::connect_arg<&delegate_functor::operator()>, &other})); - ASSERT_NE(lhs.instance(), rhs.instance()); + ASSERT_EQ(lhs, (entt::delegate{entt::connect_arg<&delegate_functor::operator()>, functor})); + ASSERT_TRUE(lhs != rhs); + ASSERT_FALSE(lhs == rhs); + ASSERT_NE(lhs, rhs); + + rhs.connect<&delegate_functor::operator()>(functor); + + ASSERT_EQ(rhs, (entt::delegate{entt::connect_arg<&delegate_functor::operator()>, functor})); ASSERT_FALSE(lhs != rhs); ASSERT_TRUE(lhs == rhs); ASSERT_EQ(lhs, rhs); + lhs.connect<&delegate_functor::operator()>(other); + + ASSERT_EQ(lhs, (entt::delegate{entt::connect_arg<&delegate_functor::operator()>, other})); + ASSERT_NE(lhs.data(), rhs.data()); + ASSERT_TRUE(lhs != rhs); + ASSERT_FALSE(lhs == rhs); + ASSERT_NE(lhs, rhs); + + lhs.connect([](const void *ptr, int val) { return static_cast(ptr)->identity(val) * val; }, &functor); + + ASSERT_NE(lhs, (entt::delegate{[](const void *, int val) { return val + val; }, &functor})); + ASSERT_TRUE(lhs != rhs); + ASSERT_FALSE(lhs == rhs); + ASSERT_NE(lhs, rhs); + + rhs.connect([](const void *ptr, int val) { return static_cast(ptr)->identity(val) + val; }, &functor); + + ASSERT_NE(rhs, (entt::delegate{[](const void *, int val) { return val * val; }, &functor})); + ASSERT_TRUE(lhs != rhs); + ASSERT_FALSE(lhs == rhs); + ASSERT_NE(lhs, rhs); + lhs.reset(); ASSERT_EQ(lhs, (entt::delegate{})); @@ -157,45 +234,72 @@ TEST(Delegate, ConstNonConstNoExcept) { entt::delegate delegate; const_nonconst_noexcept functor; - delegate.connect<&const_nonconst_noexcept::f>(&functor); + delegate.connect<&const_nonconst_noexcept::f>(functor); delegate(); - delegate.connect<&const_nonconst_noexcept::g>(&functor); + delegate.connect<&const_nonconst_noexcept::g>(functor); delegate(); - delegate.connect<&const_nonconst_noexcept::h>(&functor); + delegate.connect<&const_nonconst_noexcept::h>(functor); delegate(); - delegate.connect<&const_nonconst_noexcept::i>(&functor); + delegate.connect<&const_nonconst_noexcept::i>(functor); delegate(); ASSERT_EQ(functor.cnt, 4); } -TEST(Delegate, DeducedGuidelines) { +TEST(Delegate, DeductionGuide) { const_nonconst_noexcept functor; - const int value = 0; + int value = 0; + + entt::delegate func{entt::connect_arg<&delegate_function>}; + entt::delegate curried_func_with_ref{entt::connect_arg<&curried_by_ref>, value}; + entt::delegate curried_func_with_const_ref{entt::connect_arg<&curried_by_ref>, std::as_const(value)}; + entt::delegate curried_func_with_ptr{entt::connect_arg<&curried_by_ptr>, &value}; + entt::delegate curried_func_with_const_ptr{entt::connect_arg<&curried_by_ptr>, &std::as_const(value)}; + entt::delegate member_func_f{entt::connect_arg<&const_nonconst_noexcept::f>, functor}; + entt::delegate member_func_g{entt::connect_arg<&const_nonconst_noexcept::g>, functor}; + entt::delegate member_func_h{entt::connect_arg<&const_nonconst_noexcept::h>, &functor}; + entt::delegate member_func_h_const{entt::connect_arg<&const_nonconst_noexcept::h>, &std::as_const(functor)}; + entt::delegate member_func_i{entt::connect_arg<&const_nonconst_noexcept::i>, functor}; + entt::delegate member_func_i_const{entt::connect_arg<&const_nonconst_noexcept::i>, std::as_const(functor)}; + entt::delegate data_member_u{entt::connect_arg<&const_nonconst_noexcept::u>, functor}; + entt::delegate data_member_v{entt::connect_arg<&const_nonconst_noexcept::v>, &functor}; + entt::delegate data_member_v_const{entt::connect_arg<&const_nonconst_noexcept::v>, &std::as_const(functor)}; + entt::delegate lambda{+[](const void *, int) { return 0; }}; + + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); + static_assert(std::is_same_v); - entt::delegate func_deduced{entt::connect_arg<&delegate_function>}; - entt::delegate curried_func_deduced{entt::connect_arg<&curried_function>, &value}; - entt::delegate member_f_deduced{entt::connect_arg<&const_nonconst_noexcept::f>, &functor}; - entt::delegate member_g_deduced{entt::connect_arg<&const_nonconst_noexcept::g>, &functor}; - entt::delegate member_h_deduced{entt::connect_arg<&const_nonconst_noexcept::h>, &functor}; - entt::delegate member_i_deduced{entt::connect_arg<&const_nonconst_noexcept::i>, &functor}; - - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - static_assert(std::is_same_v); - - ASSERT_TRUE(func_deduced); - ASSERT_TRUE(curried_func_deduced); - ASSERT_TRUE(member_f_deduced); - ASSERT_TRUE(member_g_deduced); - ASSERT_TRUE(member_h_deduced); - ASSERT_TRUE(member_i_deduced); + ASSERT_TRUE(func); + ASSERT_TRUE(curried_func_with_ref); + ASSERT_TRUE(curried_func_with_const_ref); + ASSERT_TRUE(curried_func_with_ptr); + ASSERT_TRUE(curried_func_with_const_ptr); + ASSERT_TRUE(member_func_f); + ASSERT_TRUE(member_func_g); + ASSERT_TRUE(member_func_h); + ASSERT_TRUE(member_func_h_const); + ASSERT_TRUE(member_func_i); + ASSERT_TRUE(member_func_i_const); + ASSERT_TRUE(data_member_u); + ASSERT_TRUE(data_member_v); + ASSERT_TRUE(data_member_v_const); + ASSERT_TRUE(lambda); } TEST(Delegate, ConstInstance) { @@ -204,7 +308,7 @@ TEST(Delegate, ConstInstance) { ASSERT_FALSE(delegate); - delegate.connect<&delegate_functor::identity>(&functor); + delegate.connect<&delegate_functor::identity>(functor); ASSERT_TRUE(delegate); ASSERT_EQ(delegate(3), 3); @@ -215,14 +319,37 @@ TEST(Delegate, ConstInstance) { ASSERT_EQ(delegate, entt::delegate{}); } +TEST(Delegate, NonConstReference) { + entt::delegate delegate; + delegate.connect<&non_const_reference>(); + int value = 3; + + ASSERT_EQ(delegate(value), value); + ASSERT_EQ(value, 9); +} + +TEST(Delegate, MoveOnlyType) { + entt::delegate)> delegate; + auto ptr = std::make_unique(3); + delegate.connect<&move_only_type>(); + + ASSERT_EQ(delegate(std::move(ptr)), 3); + ASSERT_FALSE(ptr); +} + TEST(Delegate, CurriedFunction) { entt::delegate delegate; const auto value = 3; - delegate.connect<&curried_function>(&value); + delegate.connect<&curried_by_ref>(value); ASSERT_TRUE(delegate); ASSERT_EQ(delegate(1), 4); + + delegate.connect<&curried_by_ptr>(&value); + + ASSERT_TRUE(delegate); + ASSERT_EQ(delegate(2), 6); } TEST(Delegate, Constructors) { @@ -231,16 +358,20 @@ TEST(Delegate, Constructors) { entt::delegate empty{}; entt::delegate func{entt::connect_arg<&delegate_function>}; - entt::delegate curr{entt::connect_arg<&curried_function>, &value}; - entt::delegate member{entt::connect_arg<&delegate_functor::operator()>, &functor}; + entt::delegate ref{entt::connect_arg<&curried_by_ref>, value}; + entt::delegate ptr{entt::connect_arg<&curried_by_ptr>, &value}; + entt::delegate member{entt::connect_arg<&delegate_functor::operator()>, functor}; ASSERT_FALSE(empty); ASSERT_TRUE(func); ASSERT_EQ(9, func(3)); - ASSERT_TRUE(curr); - ASSERT_EQ(5, curr(3)); + ASSERT_TRUE(ref); + ASSERT_EQ(5, ref(3)); + + ASSERT_TRUE(ptr); + ASSERT_EQ(6, ptr(3)); ASSERT_TRUE(member); ASSERT_EQ(6, member(3)); @@ -257,3 +388,45 @@ TEST(Delegate, VoidVsNonVoidReturnType) { ASSERT_TRUE(member); ASSERT_TRUE(cmember); } + +TEST(Delegate, UnboundDataMember) { + entt::delegate delegate; + delegate.connect<&delegate_functor::data_member>(); + delegate_functor functor; + + ASSERT_EQ(delegate(functor), 42); +} + +TEST(Delegate, UnboundMemberFunction) { + entt::delegate delegate; + delegate.connect<&delegate_functor::operator()>(); + delegate_functor functor; + + ASSERT_EQ(delegate(&functor, 3), 6); +} + +TEST(Delegate, TheLessTheBetter) { + entt::delegate bound; + entt::delegate unbound; + delegate_functor functor; + + // int delegate_function(const int &); + bound.connect<&delegate_function>(); + + ASSERT_EQ(bound(3, 'c'), 9); + + // int delegate_functor::operator()(int); + bound.connect<&delegate_functor::operator()>(functor); + + ASSERT_EQ(bound(3, 'c'), 6); + + // int delegate_functor::operator()(int); + bound.connect<&delegate_functor::identity>(&functor); + + ASSERT_EQ(bound(3, 'c'), 3); + + // int delegate_functor::operator()(int); + unbound.connect<&delegate_functor::operator()>(); + + ASSERT_EQ(unbound(functor, 3, 'c'), 6); +} diff --git a/modules/entt/test/entt/signal/dispatcher.cpp b/modules/entt/test/entt/signal/dispatcher.cpp index 74efbf1..a5c678d 100644 --- a/modules/entt/test/entt/signal/dispatcher.cpp +++ b/modules/entt/test/entt/signal/dispatcher.cpp @@ -1,49 +1,89 @@ -#include +#include +#include #include -#include +#include #include struct an_event {}; struct another_event {}; -struct one_more_event {}; -ENTT_NAMED_TYPE(an_event) +// makes the type non-aggregate +struct one_more_event { + one_more_event(int) {} +}; struct receiver { - void receive(const an_event &) { ++cnt; } - void reset() { cnt = 0; } + static void forward(entt::dispatcher &dispatcher, an_event &event) { + dispatcher.enqueue(event); + } + + void receive(const an_event &) { + ++cnt; + } + + void reset() { + cnt = 0; + } + int cnt{0}; }; TEST(Dispatcher, Functionalities) { entt::dispatcher dispatcher; + entt::dispatcher other; receiver receiver; - dispatcher.trigger(); - dispatcher.enqueue(); + ASSERT_NO_FATAL_FAILURE(entt::dispatcher{std::move(dispatcher)}); + ASSERT_NO_FATAL_FAILURE(dispatcher = std::move(other)); + + ASSERT_EQ(dispatcher.size(), 0u); + ASSERT_EQ(dispatcher.size(), 0u); + + dispatcher.trigger(one_more_event{42}); + dispatcher.enqueue(42); dispatcher.update(); - dispatcher.sink().connect<&receiver::receive>(&receiver); + dispatcher.sink().connect<&receiver::receive>(receiver); dispatcher.trigger(); dispatcher.enqueue(); + ASSERT_EQ(dispatcher.size(), 0u); + ASSERT_EQ(dispatcher.size(), 1u); + ASSERT_EQ(dispatcher.size(), 1u); ASSERT_EQ(receiver.cnt, 1); - dispatcher.enqueue(); + dispatcher.enqueue(another_event{}); dispatcher.update(); + ASSERT_EQ(dispatcher.size(), 0u); + ASSERT_EQ(dispatcher.size(), 1u); + ASSERT_EQ(dispatcher.size(), 1u); ASSERT_EQ(receiver.cnt, 1); dispatcher.update(); dispatcher.trigger(); + ASSERT_EQ(dispatcher.size(), 0u); + ASSERT_EQ(dispatcher.size(), 0u); + ASSERT_EQ(receiver.cnt, 3); + + dispatcher.enqueue(); + dispatcher.clear(); + dispatcher.update(); + + dispatcher.enqueue(an_event{}); + dispatcher.clear(); + dispatcher.update(); + + ASSERT_EQ(dispatcher.size(), 0u); + ASSERT_EQ(dispatcher.size(), 0u); ASSERT_EQ(receiver.cnt, 3); receiver.reset(); an_event event{}; - dispatcher.sink().disconnect<&receiver::receive>(&receiver); + dispatcher.sink().disconnect<&receiver::receive>(receiver); dispatcher.trigger(); dispatcher.enqueue(event); dispatcher.update(); @@ -51,3 +91,110 @@ TEST(Dispatcher, Functionalities) { ASSERT_EQ(receiver.cnt, 0); } + +TEST(Dispatcher, Swap) { + entt::dispatcher dispatcher; + entt::dispatcher other; + receiver receiver; + + dispatcher.sink().connect<&receiver::receive>(receiver); + dispatcher.enqueue(); + + ASSERT_EQ(dispatcher.size(), 1u); + ASSERT_EQ(other.size(), 0u); + ASSERT_EQ(receiver.cnt, 0); + + dispatcher.swap(other); + dispatcher.update(); + + ASSERT_EQ(dispatcher.size(), 0u); + ASSERT_EQ(other.size(), 1u); + ASSERT_EQ(receiver.cnt, 0); + + other.update(); + + ASSERT_EQ(dispatcher.size(), 0u); + ASSERT_EQ(other.size(), 0u); + ASSERT_EQ(receiver.cnt, 1); +} + +TEST(Dispatcher, StopAndGo) { + entt::dispatcher dispatcher; + receiver receiver; + + dispatcher.sink().connect<&receiver::forward>(dispatcher); + dispatcher.sink().connect<&receiver::receive>(receiver); + + dispatcher.enqueue(); + dispatcher.update(); + + ASSERT_EQ(receiver.cnt, 1); + + dispatcher.sink().disconnect<&receiver::forward>(dispatcher); + dispatcher.update(); + + ASSERT_EQ(receiver.cnt, 2); +} + +TEST(Dispatcher, OpaqueDisconnect) { + entt::dispatcher dispatcher; + receiver receiver; + + dispatcher.sink().connect<&receiver::receive>(receiver); + dispatcher.trigger(); + + ASSERT_EQ(receiver.cnt, 1); + + dispatcher.disconnect(receiver); + dispatcher.trigger(); + + ASSERT_EQ(receiver.cnt, 1); +} + +TEST(Dispatcher, NamedQueue) { + using namespace entt::literals; + + entt::dispatcher dispatcher; + receiver receiver; + + dispatcher.sink("named"_hs).connect<&receiver::receive>(receiver); + dispatcher.trigger(); + + ASSERT_EQ(receiver.cnt, 0); + + dispatcher.trigger("named"_hs, an_event{}); + + ASSERT_EQ(receiver.cnt, 1); + + dispatcher.enqueue(); + dispatcher.enqueue(an_event{}); + dispatcher.enqueue_hint("named"_hs); + dispatcher.enqueue_hint("named"_hs, an_event{}); + dispatcher.update(); + + ASSERT_EQ(receiver.cnt, 1); + + dispatcher.clear(); + dispatcher.update("named"_hs); + + ASSERT_EQ(receiver.cnt, 3); + + dispatcher.enqueue_hint("named"_hs); + dispatcher.clear("named"_hs); + dispatcher.update("named"_hs); + + ASSERT_EQ(receiver.cnt, 3); +} + +TEST(Dispatcher, CustomAllocator) { + std::allocator allocator; + entt::dispatcher dispatcher{allocator}; + + ASSERT_EQ(dispatcher.get_allocator(), allocator); + ASSERT_FALSE(dispatcher.get_allocator() != allocator); + + dispatcher.enqueue(); + decltype(dispatcher) other{std::move(dispatcher), allocator}; + + ASSERT_EQ(other.size(), 1u); +} diff --git a/modules/entt/test/entt/signal/emitter.cpp b/modules/entt/test/entt/signal/emitter.cpp index c1fbc5a..a944273 100644 --- a/modules/entt/test/entt/signal/emitter.cpp +++ b/modules/entt/test/entt/signal/emitter.cpp @@ -1,44 +1,49 @@ #include -#include #include struct test_emitter: entt::emitter {}; -struct foo_event { int i; char c; }; +struct foo_event { + int i; + char c; +}; + struct bar_event {}; struct quux_event {}; -ENTT_NAMED_TYPE(foo_event) - TEST(Emitter, Clear) { test_emitter emitter; ASSERT_TRUE(emitter.empty()); - ASSERT_TRUE(emitter.empty()); - emitter.on([](const auto &, const auto &){}); + emitter.on([](auto &, const auto &) {}); + emitter.once([](const auto &, const auto &) {}); ASSERT_FALSE(emitter.empty()); ASSERT_FALSE(emitter.empty()); + ASSERT_FALSE(emitter.empty()); ASSERT_TRUE(emitter.empty()); emitter.clear(); ASSERT_FALSE(emitter.empty()); ASSERT_FALSE(emitter.empty()); + ASSERT_FALSE(emitter.empty()); ASSERT_TRUE(emitter.empty()); emitter.clear(); - ASSERT_TRUE(emitter.empty()); + ASSERT_FALSE(emitter.empty()); ASSERT_TRUE(emitter.empty()); + ASSERT_FALSE(emitter.empty()); ASSERT_TRUE(emitter.empty()); - emitter.on([](const auto &, const auto &){}); - emitter.on([](const auto &, const auto &){}); + emitter.on([](auto &, const auto &) {}); + emitter.on([](const auto &, const auto &) {}); ASSERT_FALSE(emitter.empty()); ASSERT_FALSE(emitter.empty()); + ASSERT_FALSE(emitter.empty()); ASSERT_FALSE(emitter.empty()); emitter.clear(); @@ -50,25 +55,31 @@ TEST(Emitter, Clear) { TEST(Emitter, ClearPublishing) { test_emitter emitter; - bool invoked = false; ASSERT_TRUE(emitter.empty()); - emitter.on([&invoked](const auto &, auto &em){ - invoked = true; - em.clear(); + emitter.once([](auto &, auto &em) { + em.template once([](auto &, auto &) {}); + em.template clear(); }); + emitter.on([](const auto &, auto &em) { + em.template once([](const auto &, auto &) {}); + em.template clear(); + }); + + ASSERT_FALSE(emitter.empty()); + + emitter.publish(); emitter.publish(); ASSERT_TRUE(emitter.empty()); - ASSERT_TRUE(invoked); } TEST(Emitter, On) { test_emitter emitter; - emitter.on([](const auto &, const auto &){}); + emitter.on([](auto &, const auto &) {}); ASSERT_FALSE(emitter.empty()); ASSERT_FALSE(emitter.empty()); @@ -82,7 +93,7 @@ TEST(Emitter, On) { TEST(Emitter, Once) { test_emitter emitter; - emitter.once([](const auto &, const auto &){}); + emitter.once([](const auto &, const auto &) {}); ASSERT_FALSE(emitter.empty()); ASSERT_FALSE(emitter.empty()); @@ -96,7 +107,7 @@ TEST(Emitter, Once) { TEST(Emitter, OnceAndErase) { test_emitter emitter; - auto conn = emitter.once([](const auto &, const auto &){}); + auto conn = emitter.once([](auto &, const auto &) {}); ASSERT_FALSE(emitter.empty()); ASSERT_FALSE(emitter.empty()); @@ -110,7 +121,7 @@ TEST(Emitter, OnceAndErase) { TEST(Emitter, OnAndErase) { test_emitter emitter; - auto conn = emitter.on([](const auto &, const auto &){}); + auto conn = emitter.on([](const auto &, const auto &) {}); ASSERT_FALSE(emitter.empty()); ASSERT_FALSE(emitter.empty()); diff --git a/modules/entt/test/entt/signal/sigh.cpp b/modules/entt/test/entt/signal/sigh.cpp index 14aa483..1097fc4 100644 --- a/modules/entt/test/entt/signal/sigh.cpp +++ b/modules/entt/test/entt/signal/sigh.cpp @@ -1,217 +1,577 @@ +#include #include -#include #include #include struct sigh_listener { - static void f(int &v) { v = 42; } + static void f(int &v) { + v = 42; + } + + bool g(int) { + k = !k; + return true; + } - bool g(int) { k = !k; return true; } - bool h(const int &) { return k; } + bool h(const int &) { + return k; + } - void i() {} // useless definition just because msvc does weird things if both are empty - void l() { k = k ? true : false; } + void l() { + k = true && k; + } bool k{false}; }; -template -struct test_collect_all { - std::vector vec{}; - static int f() { return 42; } - static int g() { return 3; } - bool operator()(Ret r) noexcept { - vec.push_back(r); - return true; +struct before_after { + void add(int v) { + value += v; } -}; -template<> -struct test_collect_all { - std::vector vec{}; - static void h(const void *) {} - bool operator()() noexcept { - return true; + void mul(int v) { + value *= v; + } + + static void static_add(int v) { + before_after::value += v; } + + static void static_mul(before_after &instance, int v) { + instance.value *= v; + } + + static inline int value{}; }; -template -struct test_collect_first { - std::vector vec{}; - static int f(const void *) { return 42; } - bool operator()(Ret r) noexcept { - vec.push_back(r); - return false; +struct SigH: ::testing::Test { + void SetUp() override { + before_after::value = 0; } }; struct const_nonconst_noexcept { - void f() { ++cnt; } - void g() noexcept { ++cnt; } - void h() const { ++cnt; } - void i() const noexcept { ++cnt; } + void f() { + ++cnt; + } + + void g() noexcept { + ++cnt; + } + + void h() const { + ++cnt; + } + + void i() const noexcept { + ++cnt; + } + mutable int cnt{0}; }; -TEST(SigH, Lifetime) { +TEST_F(SigH, Lifetime) { using signal = entt::sigh; - ASSERT_NO_THROW(signal{}); + ASSERT_NO_FATAL_FAILURE(signal{}); signal src{}, other{}; - ASSERT_NO_THROW(signal{src}); - ASSERT_NO_THROW(signal{std::move(other)}); - ASSERT_NO_THROW(src = other); - ASSERT_NO_THROW(src = std::move(other)); + ASSERT_NO_FATAL_FAILURE(signal{src}); + ASSERT_NO_FATAL_FAILURE(signal{std::move(other)}); + ASSERT_NO_FATAL_FAILURE(src = other); + ASSERT_NO_FATAL_FAILURE(src = std::move(other)); - ASSERT_NO_THROW(delete new signal{}); + ASSERT_NO_FATAL_FAILURE(delete new signal{}); } -TEST(SigH, Clear) { +TEST_F(SigH, Clear) { entt::sigh sigh; - sigh.sink().connect<&sigh_listener::f>(); + entt::sink sink{sigh}; - ASSERT_FALSE(sigh.sink().empty()); + sink.connect<&sigh_listener::f>(); + + ASSERT_FALSE(sink.empty()); + ASSERT_FALSE(sigh.empty()); + + sink.disconnect(static_cast(nullptr)); + + ASSERT_FALSE(sink.empty()); ASSERT_FALSE(sigh.empty()); - sigh.sink().disconnect(); + sink.disconnect(); - ASSERT_TRUE(sigh.sink().empty()); + ASSERT_TRUE(sink.empty()); ASSERT_TRUE(sigh.empty()); } -TEST(SigH, Swap) { +TEST_F(SigH, Swap) { entt::sigh sigh1; entt::sigh sigh2; + entt::sink sink1{sigh1}; + entt::sink sink2{sigh2}; - sigh1.sink().connect<&sigh_listener::f>(); + sink1.connect<&sigh_listener::f>(); - ASSERT_FALSE(sigh1.sink().empty()); - ASSERT_TRUE(sigh2.sink().empty()); + ASSERT_FALSE(sink1.empty()); + ASSERT_TRUE(sink2.empty()); ASSERT_FALSE(sigh1.empty()); ASSERT_TRUE(sigh2.empty()); - std::swap(sigh1, sigh2); + sigh1.swap(sigh2); - ASSERT_TRUE(sigh1.sink().empty()); - ASSERT_FALSE(sigh2.sink().empty()); + ASSERT_TRUE(sink1.empty()); + ASSERT_FALSE(sink2.empty()); ASSERT_TRUE(sigh1.empty()); ASSERT_FALSE(sigh2.empty()); } -TEST(SigH, Functions) { +TEST_F(SigH, Functions) { entt::sigh sigh; + entt::sink sink{sigh}; int v = 0; - sigh.sink().connect<&sigh_listener::f>(); + sink.connect<&sigh_listener::f>(); sigh.publish(v); ASSERT_FALSE(sigh.empty()); - ASSERT_EQ(static_cast::size_type>(1), sigh.size()); + ASSERT_EQ(1u, sigh.size()); ASSERT_EQ(42, v); v = 0; - sigh.sink().disconnect<&sigh_listener::f>(); + sink.disconnect<&sigh_listener::f>(); sigh.publish(v); ASSERT_TRUE(sigh.empty()); - ASSERT_EQ(static_cast::size_type>(0), sigh.size()); + ASSERT_EQ(0u, sigh.size()); + ASSERT_EQ(v, 0); +} + +TEST_F(SigH, FunctionsWithPayload) { + entt::sigh sigh; + entt::sink sink{sigh}; + int v = 0; + + sink.connect<&sigh_listener::f>(v); + sigh.publish(); + + ASSERT_FALSE(sigh.empty()); + ASSERT_EQ(1u, sigh.size()); + ASSERT_EQ(42, v); + + v = 0; + sink.disconnect<&sigh_listener::f>(v); + sigh.publish(); + + ASSERT_TRUE(sigh.empty()); + ASSERT_EQ(0u, sigh.size()); ASSERT_EQ(v, 0); - sigh.sink().connect<&sigh_listener::f>(); + sink.connect<&sigh_listener::f>(v); + sink.disconnect(v); + sigh.publish(); + + ASSERT_EQ(v, 0); } -TEST(SigH, Members) { - sigh_listener s; - sigh_listener *ptr = &s; +TEST_F(SigH, Members) { + sigh_listener l1, l2; entt::sigh sigh; + entt::sink sink{sigh}; - sigh.sink().connect<&sigh_listener::g>(ptr); + sink.connect<&sigh_listener::g>(l1); sigh.publish(42); - ASSERT_TRUE(s.k); + ASSERT_TRUE(l1.k); ASSERT_FALSE(sigh.empty()); - ASSERT_EQ(static_cast::size_type>(1), sigh.size()); + ASSERT_EQ(1u, sigh.size()); + + sink.disconnect<&sigh_listener::g>(l1); + sigh.publish(42); + + ASSERT_TRUE(l1.k); + ASSERT_TRUE(sigh.empty()); + ASSERT_EQ(0u, sigh.size()); + + sink.connect<&sigh_listener::g>(&l1); + sink.connect<&sigh_listener::h>(l2); + + ASSERT_FALSE(sigh.empty()); + ASSERT_EQ(2u, sigh.size()); + + sink.disconnect(static_cast(nullptr)); + + ASSERT_FALSE(sigh.empty()); + ASSERT_EQ(2u, sigh.size()); + + sink.disconnect(&l1); + + ASSERT_FALSE(sigh.empty()); + ASSERT_EQ(1u, sigh.size()); +} + +TEST_F(SigH, Collector) { + sigh_listener listener; + entt::sigh sigh; + entt::sink sink{sigh}; + int cnt = 0; + + sink.connect<&sigh_listener::g>(&listener); + sink.connect<&sigh_listener::h>(listener); + + auto no_return = [&listener, &cnt](bool value) { + ASSERT_TRUE(value); + listener.k = true; + ++cnt; + }; + + listener.k = true; + sigh.collect(std::move(no_return), 42); + + ASSERT_FALSE(sigh.empty()); + ASSERT_EQ(cnt, 2); + + auto bool_return = [&cnt](bool value) { + // gtest and its macro hell are sometimes really annoying... + [](auto v) { ASSERT_TRUE(v); }(value); + ++cnt; + return true; + }; + + cnt = 0; + sigh.collect(std::move(bool_return), 42); + + ASSERT_EQ(cnt, 1); +} + +TEST_F(SigH, CollectorVoid) { + sigh_listener listener; + entt::sigh sigh; + entt::sink sink{sigh}; + int cnt = 0; + + sink.connect<&sigh_listener::g>(&listener); + sink.connect<&sigh_listener::h>(listener); + sigh.collect([&cnt]() { ++cnt; }, 42); + + ASSERT_FALSE(sigh.empty()); + ASSERT_EQ(cnt, 2); + + auto test = [&cnt]() { + ++cnt; + return true; + }; + + cnt = 0; + sigh.collect(std::move(test), 42); + + ASSERT_EQ(cnt, 1); +} + +TEST_F(SigH, Connection) { + entt::sigh sigh; + entt::sink sink{sigh}; + int v = 0; + + auto conn = sink.connect<&sigh_listener::f>(); + sigh.publish(v); + + ASSERT_FALSE(sigh.empty()); + ASSERT_TRUE(conn); + ASSERT_EQ(42, v); + + v = 0; + conn.release(); + sigh.publish(v); + + ASSERT_TRUE(sigh.empty()); + ASSERT_FALSE(conn); + ASSERT_EQ(0, v); +} + +TEST_F(SigH, ScopedConnection) { + sigh_listener listener; + entt::sigh sigh; + entt::sink sink{sigh}; + + { + ASSERT_FALSE(listener.k); + + entt::scoped_connection conn = sink.connect<&sigh_listener::g>(listener); + sigh.publish(42); + + ASSERT_FALSE(sigh.empty()); + ASSERT_TRUE(listener.k); + ASSERT_TRUE(conn); + } - sigh.sink().disconnect<&sigh_listener::g>(ptr); sigh.publish(42); - ASSERT_TRUE(s.k); ASSERT_TRUE(sigh.empty()); - ASSERT_EQ(static_cast::size_type>(0), sigh.size()); + ASSERT_TRUE(listener.k); +} + +TEST_F(SigH, ScopedConnectionMove) { + sigh_listener listener; + entt::sigh sigh; + entt::sink sink{sigh}; + + entt::scoped_connection outer{sink.connect<&sigh_listener::g>(listener)}; + + ASSERT_FALSE(sigh.empty()); + ASSERT_TRUE(outer); + + { + entt::scoped_connection inner{std::move(outer)}; + + ASSERT_FALSE(listener.k); + ASSERT_FALSE(outer); + ASSERT_TRUE(inner); + + sigh.publish(42); + + ASSERT_TRUE(listener.k); + } + + ASSERT_TRUE(sigh.empty()); - sigh.sink().connect<&sigh_listener::g>(ptr); - sigh.sink().connect<&sigh_listener::h>(ptr); + outer = sink.connect<&sigh_listener::g>(listener); ASSERT_FALSE(sigh.empty()); - ASSERT_EQ(static_cast::size_type>(2), sigh.size()); + ASSERT_TRUE(outer); + + { + entt::scoped_connection inner{}; + + ASSERT_TRUE(listener.k); + ASSERT_TRUE(outer); + ASSERT_FALSE(inner); - sigh.sink().disconnect<&sigh_listener::g>(ptr); - sigh.sink().disconnect<&sigh_listener::h>(ptr); + inner = std::move(outer); + + ASSERT_FALSE(outer); + ASSERT_TRUE(inner); + + sigh.publish(42); + + ASSERT_FALSE(listener.k); + } ASSERT_TRUE(sigh.empty()); - ASSERT_EQ(static_cast::size_type>(0), sigh.size()); } -TEST(SigH, Collector) { - entt::sigh> sigh_void; - const void *fake_instance = nullptr; +TEST_F(SigH, ScopedConnectionConstructorsAndOperators) { + sigh_listener listener; + entt::sigh sigh; + entt::sink sink{sigh}; + + { + entt::scoped_connection inner{}; - sigh_void.sink().connect<&test_collect_all::h>(fake_instance); - auto collector_void = sigh_void.collect(); + ASSERT_TRUE(sigh.empty()); + ASSERT_FALSE(listener.k); + ASSERT_FALSE(inner); - ASSERT_FALSE(sigh_void.empty()); - ASSERT_TRUE(collector_void.vec.empty()); + inner = sink.connect<&sigh_listener::g>(listener); + sigh.publish(42); - entt::sigh> sigh_all; + ASSERT_FALSE(sigh.empty()); + ASSERT_TRUE(listener.k); + ASSERT_TRUE(inner); - sigh_all.sink().connect<&test_collect_all::f>(); - sigh_all.sink().connect<&test_collect_all::f>(); - sigh_all.sink().connect<&test_collect_all::g>(); - auto collector_all = sigh_all.collect(); + inner.release(); - ASSERT_FALSE(sigh_all.empty()); - ASSERT_FALSE(collector_all.vec.empty()); - ASSERT_EQ(static_cast::size_type>(2), collector_all.vec.size()); - ASSERT_EQ(42, collector_all.vec[0]); - ASSERT_EQ(3, collector_all.vec[1]); + ASSERT_TRUE(sigh.empty()); + ASSERT_FALSE(inner); - entt::sigh> sigh_first; + auto basic = sink.connect<&sigh_listener::g>(listener); + inner = std::as_const(basic); + sigh.publish(42); - sigh_first.sink().connect<&test_collect_first::f>(fake_instance); - sigh_first.sink().connect<&test_collect_first::f>(fake_instance); - auto collector_first = sigh_first.collect(); + ASSERT_FALSE(sigh.empty()); + ASSERT_FALSE(listener.k); + ASSERT_TRUE(inner); + } + + sigh.publish(42); - ASSERT_FALSE(sigh_first.empty()); - ASSERT_FALSE(collector_first.vec.empty()); - ASSERT_EQ(static_cast::size_type>(1), collector_first.vec.size()); - ASSERT_EQ(42, collector_first.vec[0]); + ASSERT_TRUE(sigh.empty()); + ASSERT_FALSE(listener.k); } -TEST(SigH, ConstNonConstNoExcept) { +TEST_F(SigH, ConstNonConstNoExcept) { entt::sigh sigh; + entt::sink sink{sigh}; const_nonconst_noexcept functor; const const_nonconst_noexcept cfunctor; - sigh.sink().connect<&const_nonconst_noexcept::f>(&functor); - sigh.sink().connect<&const_nonconst_noexcept::g>(&functor); - sigh.sink().connect<&const_nonconst_noexcept::h>(&cfunctor); - sigh.sink().connect<&const_nonconst_noexcept::i>(&cfunctor); + sink.connect<&const_nonconst_noexcept::f>(functor); + sink.connect<&const_nonconst_noexcept::g>(&functor); + sink.connect<&const_nonconst_noexcept::h>(cfunctor); + sink.connect<&const_nonconst_noexcept::i>(&cfunctor); sigh.publish(); ASSERT_EQ(functor.cnt, 2); ASSERT_EQ(cfunctor.cnt, 2); - sigh.sink().disconnect<&const_nonconst_noexcept::f>(&functor); - sigh.sink().disconnect<&const_nonconst_noexcept::g>(&functor); - sigh.sink().disconnect<&const_nonconst_noexcept::h>(&cfunctor); - sigh.sink().disconnect<&const_nonconst_noexcept::i>(&cfunctor); + sink.disconnect<&const_nonconst_noexcept::f>(functor); + sink.disconnect<&const_nonconst_noexcept::g>(&functor); + sink.disconnect<&const_nonconst_noexcept::h>(cfunctor); + sink.disconnect<&const_nonconst_noexcept::i>(&cfunctor); sigh.publish(); ASSERT_EQ(functor.cnt, 2); ASSERT_EQ(cfunctor.cnt, 2); } + +TEST_F(SigH, BeforeFunction) { + entt::sigh sigh; + entt::sink sink{sigh}; + before_after functor; + + sink.connect<&before_after::add>(functor); + sink.connect<&before_after::static_add>(); + sink.before<&before_after::static_add>().connect<&before_after::mul>(functor); + sigh.publish(2); + + ASSERT_EQ(functor.value, 6); +} + +TEST_F(SigH, BeforeMemberFunction) { + entt::sigh sigh; + entt::sink sink{sigh}; + before_after functor; + + sink.connect<&before_after::static_add>(); + sink.connect<&before_after::add>(functor); + sink.before<&before_after::add>(functor).connect<&before_after::mul>(functor); + sigh.publish(2); + + ASSERT_EQ(functor.value, 6); +} + +TEST_F(SigH, BeforeFunctionWithPayload) { + entt::sigh sigh; + entt::sink sink{sigh}; + before_after functor; + + sink.connect<&before_after::static_add>(); + sink.connect<&before_after::static_mul>(functor); + sink.before<&before_after::static_mul>(functor).connect<&before_after::add>(functor); + sigh.publish(2); + + ASSERT_EQ(functor.value, 8); +} + +TEST_F(SigH, BeforeInstanceOrPayload) { + entt::sigh sigh; + entt::sink sink{sigh}; + before_after functor; + + sink.connect<&before_after::static_mul>(functor); + sink.connect<&before_after::add>(functor); + sink.before(functor).connect<&before_after::static_add>(); + sigh.publish(2); + + ASSERT_EQ(functor.value, 6); +} + +TEST_F(SigH, BeforeAnythingElse) { + entt::sigh sigh; + entt::sink sink{sigh}; + before_after functor; + + sink.connect<&before_after::add>(functor); + sink.before().connect<&before_after::mul>(functor); + sigh.publish(2); + + ASSERT_EQ(functor.value, 2); +} + +TEST_F(SigH, BeforeListenerNotPresent) { + entt::sigh sigh; + entt::sink sink{sigh}; + before_after functor; + + sink.connect<&before_after::mul>(functor); + sink.before<&before_after::add>(&functor).connect<&before_after::add>(functor); + sigh.publish(2); + + ASSERT_EQ(functor.value, 2); +} + +TEST_F(SigH, UnboundDataMember) { + sigh_listener listener; + entt::sigh sigh; + entt::sink sink{sigh}; + + ASSERT_FALSE(listener.k); + + sink.connect<&sigh_listener::k>(); + sigh.collect([](bool &value) { value = !value; }, listener); + + ASSERT_TRUE(listener.k); +} + +TEST_F(SigH, UnboundMemberFunction) { + sigh_listener listener; + entt::sigh sigh; + entt::sink sink{sigh}; + + ASSERT_FALSE(listener.k); + + sink.connect<&sigh_listener::g>(); + sigh.publish(&listener, 42); + + ASSERT_TRUE(listener.k); +} + +TEST_F(SigH, CustomAllocator) { + std::allocator allocator; + entt::sigh sigh{allocator}; + + ASSERT_EQ(sigh.get_allocator(), allocator); + ASSERT_FALSE(sigh.get_allocator() != allocator); + ASSERT_TRUE(sigh.empty()); + + entt::sink sink{sigh}; + sigh_listener listener; + sink.template connect<&sigh_listener::g>(listener); + + decltype(sigh) copy{sigh, allocator}; + sink.disconnect(listener); + + ASSERT_TRUE(sigh.empty()); + ASSERT_FALSE(copy.empty()); + + sigh = copy; + + ASSERT_FALSE(sigh.empty()); + ASSERT_FALSE(copy.empty()); + + decltype(sigh) move{std::move(copy), allocator}; + + ASSERT_TRUE(copy.empty()); + ASSERT_FALSE(move.empty()); + + sink = entt::sink{move}; + sink.disconnect(&listener); + + ASSERT_TRUE(copy.empty()); + ASSERT_TRUE(move.empty()); + + sink.template connect<&sigh_listener::g>(listener); + copy.swap(move); + + ASSERT_FALSE(copy.empty()); + ASSERT_TRUE(move.empty()); + + sink = entt::sink{copy}; + sink.disconnect(); + + ASSERT_TRUE(copy.empty()); + ASSERT_TRUE(move.empty()); +} diff --git a/modules/entt/test/example/custom_identifier.cpp b/modules/entt/test/example/custom_identifier.cpp new file mode 100644 index 0000000..5611da3 --- /dev/null +++ b/modules/entt/test/example/custom_identifier.cpp @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include + +struct entity_id { + using entity_type = std::uint32_t; + static constexpr auto null = entt::null; + + constexpr entity_id(entity_type value = null) ENTT_NOEXCEPT + : entt{value} {} + + constexpr entity_id(const entity_id &other) ENTT_NOEXCEPT + : entt{other.entt} {} + + constexpr operator entity_type() const ENTT_NOEXCEPT { + return entt; + } + +private: + entity_type entt; +}; + +TEST(Example, CustomIdentifier) { + entt::basic_registry registry{}; + entity_id entity{}; + + ASSERT_FALSE(registry.valid(entity)); + ASSERT_TRUE(entity == entt::null); + + entity = registry.create(); + + ASSERT_TRUE(registry.valid(entity)); + ASSERT_TRUE(entity != entt::null); + + ASSERT_FALSE((registry.all_of(entity))); + ASSERT_EQ(registry.try_get(entity), nullptr); + + registry.emplace(entity, 42); + + ASSERT_TRUE((registry.any_of(entity))); + ASSERT_EQ(registry.get(entity), 42); + + registry.destroy(entity); + + ASSERT_FALSE(registry.valid(entity)); + ASSERT_TRUE(entity != entt::null); + + entity = registry.create(); + + ASSERT_TRUE(registry.valid(entity)); + ASSERT_TRUE(entity != entt::null); +} diff --git a/modules/entt/test/example/entity_copy.cpp b/modules/entt/test/example/entity_copy.cpp new file mode 100644 index 0000000..fdbd157 --- /dev/null +++ b/modules/entt/test/example/entity_copy.cpp @@ -0,0 +1,68 @@ +#include +#include +#include + +enum class my_entity : entt::id_type {}; + +TEST(Example, EntityCopy) { + using namespace entt::literals; + + entt::registry registry{}; + auto &&custom = registry.storage("custom"_hs); + + const auto src = registry.create(); + const auto dst = registry.create(); + const auto other = registry.create(); + + custom.emplace(src, 1.); + registry.emplace(src, 42); + registry.emplace(src, 'c'); + registry.emplace(other, 3.); + + ASSERT_TRUE(custom.contains(src)); + ASSERT_FALSE(registry.all_of(src)); + ASSERT_TRUE((registry.all_of(src))); + ASSERT_FALSE((registry.any_of(dst))); + ASSERT_FALSE(custom.contains(dst)); + + for(auto [id, storage]: registry.storage()) { + // discard the custom storage because why not, this is just an example after all + if(id != "custom"_hs && storage.contains(src)) { + storage.emplace(dst, storage.get(src)); + } + } + + ASSERT_TRUE((registry.all_of(dst))); + ASSERT_FALSE((registry.all_of(dst))); + ASSERT_FALSE(custom.contains(dst)); + + ASSERT_EQ(registry.get(dst), 42); + ASSERT_EQ(registry.get(dst), 'c'); +} + +TEST(Example, DifferentRegistryTypes) { + using namespace entt::literals; + + entt::basic_registry registry{}; + entt::basic_registry other{}; + + static_cast(registry.storage()); + static_cast(other.storage()); + + const auto src = registry.create(); + const auto dst = other.create(); + + registry.emplace(src, 42); + registry.emplace(src, 'c'); + + for(auto [id, storage]: registry.storage()) { + if(auto it = other.storage(id); it != other.storage().end() && storage.contains(src)) { + it->second.emplace(dst, storage.get(src)); + } + } + + ASSERT_TRUE((registry.all_of(src))); + ASSERT_FALSE(other.all_of(dst)); + ASSERT_TRUE(other.all_of(dst)); + ASSERT_EQ(other.get(dst), 42); +} diff --git a/modules/entt/test/example/signal_less.cpp b/modules/entt/test/example/signal_less.cpp new file mode 100644 index 0000000..5964b2d --- /dev/null +++ b/modules/entt/test/example/signal_less.cpp @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include + +template +struct entt::storage_traits { + // no signal regardless of component type ... + using storage_type = basic_storage; +}; + +template +struct entt::storage_traits { + // ... unless it's char, because yes. + using storage_type = sigh_storage_mixin>; +}; + +template +struct has_on_construct: std::false_type {}; + +template +struct has_on_construct::storage_type::on_construct)>>: std::true_type {}; + +template +inline constexpr auto has_on_construct_v = has_on_construct::value; + +TEST(Example, SignalLess) { + // invoking registry::on_construct is a compile-time error + static_assert(!has_on_construct_v); + static_assert(has_on_construct_v); + + entt::registry registry; + const entt::entity entity[1u]{registry.create()}; + + // literally a test for storage_adapter_mixin + registry.emplace(entity[0], 0); + registry.erase(entity[0]); + registry.insert(std::begin(entity), std::end(entity), 3); + registry.patch(entity[0], [](auto &value) { value = 42; }); + + ASSERT_EQ(registry.get(entity[0]), 42); +} diff --git a/modules/entt/test/lib/a_module.cpp b/modules/entt/test/lib/a_module.cpp deleted file mode 100644 index 44f641f..0000000 --- a/modules/entt/test/lib/a_module.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include -#include -#include "types.h" - -#ifndef LIB_EXPORT -#if defined _WIN32 || defined __CYGWIN__ -#define LIB_EXPORT __declspec(dllexport) -#elif defined __GNUC__ -#define LIB_EXPORT __attribute__((visibility("default"))) -#else -#define LIB_EXPORT -#endif -#endif - -ENTT_NAMED_TYPE(int) -ENTT_NAMED_TYPE(char) -ENTT_NAMED_TYPE(double) -ENTT_NAMED_TYPE(float) - -LIB_EXPORT typename entt::registry::component_type a_module_int_type() { - entt::registry registry; - - (void)registry.type(); - (void)registry.type(); - - return registry.type(); -} - -LIB_EXPORT typename entt::registry::component_type a_module_char_type() { - entt::registry registry; - - (void)registry.type(); - (void)registry.type(); - - return registry.type(); -} - -LIB_EXPORT void update_position(int delta, entt::registry ®istry) { - registry.view().each([delta](auto &pos, auto &vel) { - pos.x += delta * vel.dx; - pos.y += delta * vel.dy; - }); -} - -LIB_EXPORT void trigger_another_event(entt::dispatcher &dispatcher) { - dispatcher.trigger(); -} - -LIB_EXPORT void emit_another_event(test_emitter &emitter) { - emitter.publish(); -} diff --git a/modules/entt/test/lib/another_module.cpp b/modules/entt/test/lib/another_module.cpp deleted file mode 100644 index b1155d2..0000000 --- a/modules/entt/test/lib/another_module.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include -#include -#include -#include "types.h" - -#ifndef LIB_EXPORT -#if defined _WIN32 || defined __CYGWIN__ -#define LIB_EXPORT __declspec(dllexport) -#elif defined __GNUC__ -#define LIB_EXPORT __attribute__((visibility("default"))) -#else -#define LIB_EXPORT -#endif -#endif - -ENTT_NAMED_TYPE(int) -ENTT_NAMED_TYPE(char) -ENTT_NAMED_TYPE(double) -ENTT_NAMED_TYPE(float) - -LIB_EXPORT typename entt::registry::component_type another_module_int_type() { - entt::registry registry; - - (void)registry.type(); - (void)registry.type(); - (void)registry.type(); - (void)registry.type(); - (void)registry.type(); - - return registry.type(); -} - -LIB_EXPORT typename entt::registry::component_type another_module_char_type() { - entt::registry registry; - - (void)registry.type(); - (void)registry.type(); - (void)registry.type(); - (void)registry.type(); - (void)registry.type(); - - return registry.type(); -} - -LIB_EXPORT void assign_velocity(int vel, entt::registry ®istry) { - for(auto entity: registry.view()) { - registry.assign(entity, vel, vel); - } -} - -LIB_EXPORT void trigger_an_event(int payload, entt::dispatcher &dispatcher) { - dispatcher.trigger(payload); -} - -LIB_EXPORT void emit_an_event(int payload, test_emitter &emitter) { - emitter.publish(payload); -} diff --git a/modules/entt/test/lib/dispatcher/lib.cpp b/modules/entt/test/lib/dispatcher/lib.cpp new file mode 100644 index 0000000..2a579c2 --- /dev/null +++ b/modules/entt/test/lib/dispatcher/lib.cpp @@ -0,0 +1,8 @@ +#include +#include +#include "types.h" + +ENTT_API void trigger(entt::dispatcher &dispatcher) { + dispatcher.trigger(); + dispatcher.trigger(message{42}); +} diff --git a/modules/entt/test/lib/dispatcher/main.cpp b/modules/entt/test/lib/dispatcher/main.cpp new file mode 100644 index 0000000..edffe25 --- /dev/null +++ b/modules/entt/test/lib/dispatcher/main.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include +#include "types.h" + +ENTT_API void trigger(entt::dispatcher &); + +struct listener { + void on(message msg) { + value = msg.payload; + } + + int value{}; +}; + +TEST(Lib, Dispatcher) { + entt::dispatcher dispatcher; + listener listener; + + ASSERT_EQ(listener.value, 0); + + dispatcher.sink().connect(&listener::on)>(listener); + trigger(dispatcher); + + ASSERT_EQ(listener.value, 42); +} diff --git a/modules/entt/test/lib/dispatcher/types.h b/modules/entt/test/lib/dispatcher/types.h new file mode 100644 index 0000000..d130f42 --- /dev/null +++ b/modules/entt/test/lib/dispatcher/types.h @@ -0,0 +1,12 @@ +#ifndef ENTT_LIB_DISPATCHER_TYPES_H +#define ENTT_LIB_DISPATCHER_TYPES_H + +#include + +struct ENTT_API message { + int payload; +}; + +struct ENTT_API event {}; + +#endif diff --git a/modules/entt/test/lib/dispatcher_plugin/main.cpp b/modules/entt/test/lib/dispatcher_plugin/main.cpp new file mode 100644 index 0000000..1192faa --- /dev/null +++ b/modules/entt/test/lib/dispatcher_plugin/main.cpp @@ -0,0 +1,35 @@ +#define CR_HOST + +#include +#include +#include +#include +#include "types.h" + +struct listener { + void on(message msg) { + value = msg.payload; + } + + int value{}; +}; + +TEST(Lib, Dispatcher) { + entt::dispatcher dispatcher; + listener listener; + + ASSERT_EQ(listener.value, 0); + + dispatcher.sink().connect<&listener::on>(listener); + + cr_plugin ctx; + cr_plugin_load(ctx, PLUGIN); + + ctx.userdata = &dispatcher; + cr_plugin_update(ctx); + + ASSERT_EQ(listener.value, 42); + + dispatcher = {}; + cr_plugin_close(ctx); +} diff --git a/modules/entt/test/lib/dispatcher_plugin/plugin.cpp b/modules/entt/test/lib/dispatcher_plugin/plugin.cpp new file mode 100644 index 0000000..f9afafe --- /dev/null +++ b/modules/entt/test/lib/dispatcher_plugin/plugin.cpp @@ -0,0 +1,19 @@ +#include +#include +#include "types.h" + +CR_EXPORT int cr_main(cr_plugin *ctx, cr_op operation) { + switch(operation) { + case CR_STEP: + static_cast(ctx->userdata)->trigger(); + static_cast(ctx->userdata)->trigger(message{42}); + break; + case CR_CLOSE: + case CR_LOAD: + case CR_UNLOAD: + // nothing to do here, this is only a test. + break; + } + + return 0; +} diff --git a/modules/entt/test/lib/dispatcher_plugin/types.h b/modules/entt/test/lib/dispatcher_plugin/types.h new file mode 100644 index 0000000..b070710 --- /dev/null +++ b/modules/entt/test/lib/dispatcher_plugin/types.h @@ -0,0 +1,10 @@ +#ifndef ENTT_LIB_DISPATCHER_PLUGIN_TYPES_H +#define ENTT_LIB_DISPATCHER_PLUGIN_TYPES_H + +struct message { + int payload; +}; + +struct event {}; + +#endif diff --git a/modules/entt/test/lib/emitter/lib.cpp b/modules/entt/test/lib/emitter/lib.cpp new file mode 100644 index 0000000..e9c4594 --- /dev/null +++ b/modules/entt/test/lib/emitter/lib.cpp @@ -0,0 +1,9 @@ +#include +#include +#include "types.h" + +ENTT_API void emit(test_emitter &emitter) { + emitter.publish(); + emitter.publish(42); + emitter.publish(3); +} diff --git a/modules/entt/test/lib/emitter/main.cpp b/modules/entt/test/lib/emitter/main.cpp new file mode 100644 index 0000000..9c879a0 --- /dev/null +++ b/modules/entt/test/lib/emitter/main.cpp @@ -0,0 +1,18 @@ +#include +#include +#include +#include "types.h" + +ENTT_API void emit(test_emitter &); + +TEST(Lib, Emitter) { + test_emitter emitter; + int value{}; + + ASSERT_EQ(value, 0); + + emitter.once([&](message msg, test_emitter &) { value = msg.payload; }); + emit(emitter); + + ASSERT_EQ(value, 42); +} diff --git a/modules/entt/test/lib/emitter/types.h b/modules/entt/test/lib/emitter/types.h new file mode 100644 index 0000000..ec2c133 --- /dev/null +++ b/modules/entt/test/lib/emitter/types.h @@ -0,0 +1,16 @@ +#ifndef ENTT_LIB_EMITTER_TYPES_H +#define ENTT_LIB_EMITTER_TYPES_H + +#include +#include + +struct ENTT_API test_emitter + : entt::emitter {}; + +struct ENTT_API message { + int payload; +}; + +struct ENTT_API event {}; + +#endif diff --git a/modules/entt/test/lib/emitter_plugin/main.cpp b/modules/entt/test/lib/emitter_plugin/main.cpp new file mode 100644 index 0000000..e6e9154 --- /dev/null +++ b/modules/entt/test/lib/emitter_plugin/main.cpp @@ -0,0 +1,26 @@ +#define CR_HOST + +#include +#include +#include +#include "types.h" + +TEST(Lib, Emitter) { + test_emitter emitter; + int value{}; + + ASSERT_EQ(value, 0); + + emitter.once([&](message msg, test_emitter &) { value = msg.payload; }); + + cr_plugin ctx; + cr_plugin_load(ctx, PLUGIN); + + ctx.userdata = &emitter; + cr_plugin_update(ctx); + + ASSERT_EQ(value, 42); + + emitter = {}; + cr_plugin_close(ctx); +} diff --git a/modules/entt/test/lib/emitter_plugin/plugin.cpp b/modules/entt/test/lib/emitter_plugin/plugin.cpp new file mode 100644 index 0000000..b5a46d6 --- /dev/null +++ b/modules/entt/test/lib/emitter_plugin/plugin.cpp @@ -0,0 +1,20 @@ +#include +#include +#include "types.h" + +CR_EXPORT int cr_main(cr_plugin *ctx, cr_op operation) { + switch(operation) { + case CR_STEP: + static_cast(ctx->userdata)->publish(); + static_cast(ctx->userdata)->publish(42); + static_cast(ctx->userdata)->publish(3); + break; + case CR_CLOSE: + case CR_LOAD: + case CR_UNLOAD: + // nothing to do here, this is only a test. + break; + } + + return 0; +} diff --git a/modules/entt/test/lib/emitter_plugin/types.h b/modules/entt/test/lib/emitter_plugin/types.h new file mode 100644 index 0000000..a4c7824 --- /dev/null +++ b/modules/entt/test/lib/emitter_plugin/types.h @@ -0,0 +1,15 @@ +#ifndef ENTT_LIB_EMITTER_PLUGIN_TYPES_H +#define ENTT_LIB_EMITTER_PLUGIN_TYPES_H + +#include + +struct test_emitter + : entt::emitter {}; + +struct message { + int payload; +}; + +struct event {}; + +#endif diff --git a/modules/entt/test/lib/lib.cpp b/modules/entt/test/lib/lib.cpp deleted file mode 100644 index aec85fd..0000000 --- a/modules/entt/test/lib/lib.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include -#include -#include -#include -#include "types.h" - -extern typename entt::registry::component_type a_module_int_type(); -extern typename entt::registry::component_type a_module_char_type(); -extern typename entt::registry::component_type another_module_int_type(); -extern typename entt::registry::component_type another_module_char_type(); - -extern void update_position(int delta, entt::registry &); -extern void assign_velocity(int, entt::registry &); - -extern void trigger_an_event(int, entt::dispatcher &); -extern void trigger_another_event(entt::dispatcher &); - -struct listener { - void on_an_event(an_event event) { value = event.payload; } - void on_another_event(another_event) {} - - int value; -}; - -ENTT_NAMED_TYPE(int) -ENTT_NAMED_TYPE(char) - -TEST(Lib, Types) { - entt::registry registry; - - ASSERT_EQ(registry.type(), registry.type()); - ASSERT_EQ(registry.type(), registry.type()); - - ASSERT_EQ(registry.type(), a_module_int_type()); - ASSERT_EQ(registry.type(), a_module_char_type()); - ASSERT_EQ(registry.type(), a_module_int_type()); - ASSERT_EQ(registry.type(), a_module_char_type()); - - ASSERT_EQ(registry.type(), another_module_char_type()); - ASSERT_EQ(registry.type(), another_module_int_type()); - ASSERT_EQ(registry.type(), another_module_char_type()); - ASSERT_EQ(registry.type(), another_module_int_type()); -} - -TEST(Lib, Registry) { - entt::registry registry; - - for(auto i = 0; i < 3; ++i) { - const auto entity = registry.create(); - registry.assign(entity, i, i+1); - } - - assign_velocity(2, registry); - - ASSERT_EQ(registry.size(), entt::registry::size_type{3}); - ASSERT_EQ(registry.size(), entt::registry::size_type{3}); - - update_position(1, registry); - - registry.view().each([](auto entity, auto &position) { - ASSERT_EQ(position.x, entity + 2); - ASSERT_EQ(position.y, entity + 3); - }); -} - -TEST(Lib, Dispatcher) { - entt::dispatcher dispatcher; - listener listener; - - dispatcher.sink().connect<&listener::on_an_event>(&listener); - dispatcher.sink().connect<&listener::on_another_event>(&listener); - - listener.value = 0; - - trigger_another_event(dispatcher); - trigger_an_event(3, dispatcher); - - ASSERT_EQ(listener.value, 3); -} - -TEST(Lib, Emitter) { - test_emitter emitter; - - emitter.once([](another_event, test_emitter &) {}); - emitter.once([](an_event event, test_emitter &) { - ASSERT_EQ(event.payload, 3); - }); - - emitter.publish(3); - emitter.publish(); - - emitter.once([](an_event event, test_emitter &) { - ASSERT_EQ(event.payload, 42); - }); - - emitter.publish(); - emitter.publish(42); -} diff --git a/modules/entt/test/lib/meta/lib.cpp b/modules/entt/test/lib/meta/lib.cpp new file mode 100644 index 0000000..c508c01 --- /dev/null +++ b/modules/entt/test/lib/meta/lib.cpp @@ -0,0 +1,34 @@ +#include +#include +#include +#include +#include "types.h" + +position create_position(int x, int y) { + return position{x, y}; +} + +ENTT_API void set_up() { + using namespace entt::literals; + + entt::meta() + .type("position"_hs) + .ctor<&create_position>() + .data<&position::x>("x"_hs) + .data<&position::y>("y"_hs); + + entt::meta() + .type("velocity"_hs) + .ctor<>() + .data<&velocity::dx>("dx"_hs) + .data<&velocity::dy>("dy"_hs); +} + +ENTT_API void tear_down() { + entt::meta_reset(); + entt::meta_reset(); +} + +ENTT_API entt::meta_any wrap_int(int value) { + return value; +} diff --git a/modules/entt/test/lib/meta/main.cpp b/modules/entt/test/lib/meta/main.cpp new file mode 100644 index 0000000..d1a7713 --- /dev/null +++ b/modules/entt/test/lib/meta/main.cpp @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include +#include +#include "types.h" + +ENTT_API void set_up(); +ENTT_API void tear_down(); +ENTT_API entt::meta_any wrap_int(int); + +TEST(Lib, Meta) { + using namespace entt::literals; + + ASSERT_FALSE(entt::resolve("position"_hs)); + ASSERT_FALSE(entt::resolve("velocity"_hs)); + + set_up(); + entt::meta().conv(); + + ASSERT_TRUE(entt::resolve("position"_hs)); + ASSERT_TRUE(entt::resolve("velocity"_hs)); + + ASSERT_EQ(entt::resolve(), entt::resolve("position"_hs)); + ASSERT_EQ(entt::resolve(), entt::resolve("velocity"_hs)); + + auto pos = entt::resolve("position"_hs).construct(42., 3.); + auto vel = entt::resolve("velocity"_hs).construct(); + + ASSERT_TRUE(pos && vel); + + ASSERT_EQ(pos.type().data("x"_hs).type(), entt::resolve()); + ASSERT_NE(pos.type().data("y"_hs).get(pos).try_cast(), nullptr); + ASSERT_EQ(pos.type().data("x"_hs).get(pos).cast(), 42); + ASSERT_EQ(pos.type().data("y"_hs).get(pos).cast(), 3); + + ASSERT_EQ(vel.type().data("dx"_hs).type(), entt::resolve()); + ASSERT_TRUE(vel.type().data("dy"_hs).get(vel).allow_cast()); + ASSERT_EQ(vel.type().data("dx"_hs).get(vel).cast(), 0.); + ASSERT_EQ(vel.type().data("dy"_hs).get(vel).cast(), 0.); + + ASSERT_EQ(wrap_int(42).type(), entt::resolve()); + ASSERT_EQ(wrap_int(42).cast(), 42); + + tear_down(); + + ASSERT_FALSE(entt::resolve("position"_hs)); + ASSERT_FALSE(entt::resolve("velocity"_hs)); +} diff --git a/modules/entt/test/lib/meta/types.h b/modules/entt/test/lib/meta/types.h new file mode 100644 index 0000000..ad2a812 --- /dev/null +++ b/modules/entt/test/lib/meta/types.h @@ -0,0 +1,14 @@ +#ifndef ENTT_LIB_META_TYPES_H +#define ENTT_LIB_META_TYPES_H + +struct position { + int x; + int y; +}; + +struct velocity { + double dx; + double dy; +}; + +#endif diff --git a/modules/entt/test/lib/meta_plugin/main.cpp b/modules/entt/test/lib/meta_plugin/main.cpp new file mode 100644 index 0000000..999168c --- /dev/null +++ b/modules/entt/test/lib/meta_plugin/main.cpp @@ -0,0 +1,56 @@ +#define CR_HOST + +#include +#include +#include +#include +#include +#include +#include "types.h" + +TEST(Lib, Meta) { + using namespace entt::literals; + + ASSERT_FALSE(entt::resolve("position"_hs)); + + userdata ud{}; + + cr_plugin ctx; + cr_plugin_load(ctx, PLUGIN); + + ctx.userdata = &ud; + cr_plugin_update(ctx); + + entt::meta().conv(); + + ASSERT_TRUE(entt::resolve("position"_hs)); + ASSERT_TRUE(entt::resolve("velocity"_hs)); + + auto pos = entt::resolve("position"_hs).construct(42., 3.); + auto vel = entt::resolve("velocity"_hs).construct(); + + ASSERT_TRUE(pos && vel); + + ASSERT_EQ(pos.type().data("x"_hs).type(), entt::resolve()); + ASSERT_NE(pos.type().data("y"_hs).get(pos).try_cast(), nullptr); + ASSERT_EQ(pos.type().data("x"_hs).get(pos).cast(), 42); + ASSERT_EQ(pos.type().data("y"_hs).get(pos).cast(), 3); + + ASSERT_EQ(vel.type().data("dx"_hs).type(), entt::resolve()); + ASSERT_TRUE(vel.type().data("dy"_hs).get(vel).allow_cast()); + ASSERT_EQ(vel.type().data("dx"_hs).get(vel).cast(), 0.); + ASSERT_EQ(vel.type().data("dy"_hs).get(vel).cast(), 0.); + + ASSERT_EQ(ud.any.type(), entt::resolve()); + ASSERT_EQ(ud.any.cast(), 42); + + // these objects have been initialized from a different context + pos.emplace(); + vel.emplace(); + ud.any.emplace(); + + cr_plugin_close(ctx); + + ASSERT_FALSE(entt::resolve("position"_hs)); + ASSERT_FALSE(entt::resolve("velocity"_hs)); +} diff --git a/modules/entt/test/lib/meta_plugin/plugin.cpp b/modules/entt/test/lib/meta_plugin/plugin.cpp new file mode 100644 index 0000000..8724fd6 --- /dev/null +++ b/modules/entt/test/lib/meta_plugin/plugin.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include +#include "types.h" + +position create_position(int x, int y) { + return position{x, y}; +} + +void set_up() { + using namespace entt::literals; + + entt::meta() + .type("position"_hs) + .ctor<&create_position>() + .data<&position::x>("x"_hs) + .data<&position::y>("y"_hs); + + entt::meta() + .type("velocity"_hs) + .ctor<>() + .data<&velocity::dx>("dx"_hs) + .data<&velocity::dy>("dy"_hs); +} + +void tear_down() { + entt::meta_reset(); + entt::meta_reset(); +} + +CR_EXPORT int cr_main(cr_plugin *ctx, cr_op operation) { + switch(operation) { + case CR_LOAD: + entt::meta_ctx::bind(static_cast(ctx->userdata)->ctx); + set_up(); + break; + case CR_STEP: + static_cast(ctx->userdata)->any = 42; + break; + case CR_UNLOAD: + case CR_CLOSE: + tear_down(); + break; + } + + return 0; +} diff --git a/modules/entt/test/lib/meta_plugin/types.h b/modules/entt/test/lib/meta_plugin/types.h new file mode 100644 index 0000000..5001d5d --- /dev/null +++ b/modules/entt/test/lib/meta_plugin/types.h @@ -0,0 +1,21 @@ +#ifndef ENTT_LIB_META_PLUGIN_TYPES_H +#define ENTT_LIB_META_PLUGIN_TYPES_H + +#include + +struct position { + int x; + int y; +}; + +struct velocity { + double dx; + double dy; +}; + +struct userdata { + entt::meta_ctx ctx; + entt::meta_any any; +}; + +#endif diff --git a/modules/entt/test/lib/meta_plugin_std/main.cpp b/modules/entt/test/lib/meta_plugin_std/main.cpp new file mode 100644 index 0000000..999168c --- /dev/null +++ b/modules/entt/test/lib/meta_plugin_std/main.cpp @@ -0,0 +1,56 @@ +#define CR_HOST + +#include +#include +#include +#include +#include +#include +#include "types.h" + +TEST(Lib, Meta) { + using namespace entt::literals; + + ASSERT_FALSE(entt::resolve("position"_hs)); + + userdata ud{}; + + cr_plugin ctx; + cr_plugin_load(ctx, PLUGIN); + + ctx.userdata = &ud; + cr_plugin_update(ctx); + + entt::meta().conv(); + + ASSERT_TRUE(entt::resolve("position"_hs)); + ASSERT_TRUE(entt::resolve("velocity"_hs)); + + auto pos = entt::resolve("position"_hs).construct(42., 3.); + auto vel = entt::resolve("velocity"_hs).construct(); + + ASSERT_TRUE(pos && vel); + + ASSERT_EQ(pos.type().data("x"_hs).type(), entt::resolve()); + ASSERT_NE(pos.type().data("y"_hs).get(pos).try_cast(), nullptr); + ASSERT_EQ(pos.type().data("x"_hs).get(pos).cast(), 42); + ASSERT_EQ(pos.type().data("y"_hs).get(pos).cast(), 3); + + ASSERT_EQ(vel.type().data("dx"_hs).type(), entt::resolve()); + ASSERT_TRUE(vel.type().data("dy"_hs).get(vel).allow_cast()); + ASSERT_EQ(vel.type().data("dx"_hs).get(vel).cast(), 0.); + ASSERT_EQ(vel.type().data("dy"_hs).get(vel).cast(), 0.); + + ASSERT_EQ(ud.any.type(), entt::resolve()); + ASSERT_EQ(ud.any.cast(), 42); + + // these objects have been initialized from a different context + pos.emplace(); + vel.emplace(); + ud.any.emplace(); + + cr_plugin_close(ctx); + + ASSERT_FALSE(entt::resolve("position"_hs)); + ASSERT_FALSE(entt::resolve("velocity"_hs)); +} diff --git a/modules/entt/test/lib/meta_plugin_std/plugin.cpp b/modules/entt/test/lib/meta_plugin_std/plugin.cpp new file mode 100644 index 0000000..8724fd6 --- /dev/null +++ b/modules/entt/test/lib/meta_plugin_std/plugin.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include +#include "types.h" + +position create_position(int x, int y) { + return position{x, y}; +} + +void set_up() { + using namespace entt::literals; + + entt::meta() + .type("position"_hs) + .ctor<&create_position>() + .data<&position::x>("x"_hs) + .data<&position::y>("y"_hs); + + entt::meta() + .type("velocity"_hs) + .ctor<>() + .data<&velocity::dx>("dx"_hs) + .data<&velocity::dy>("dy"_hs); +} + +void tear_down() { + entt::meta_reset(); + entt::meta_reset(); +} + +CR_EXPORT int cr_main(cr_plugin *ctx, cr_op operation) { + switch(operation) { + case CR_LOAD: + entt::meta_ctx::bind(static_cast(ctx->userdata)->ctx); + set_up(); + break; + case CR_STEP: + static_cast(ctx->userdata)->any = 42; + break; + case CR_UNLOAD: + case CR_CLOSE: + tear_down(); + break; + } + + return 0; +} diff --git a/modules/entt/test/lib/meta_plugin_std/types.h b/modules/entt/test/lib/meta_plugin_std/types.h new file mode 100644 index 0000000..778203e --- /dev/null +++ b/modules/entt/test/lib/meta_plugin_std/types.h @@ -0,0 +1,46 @@ +#ifndef ENTT_LIB_META_PLUGIN_TYPES_STD_H +#define ENTT_LIB_META_PLUGIN_TYPES_STD_H + +#include +#include +#include +#include + +template +struct custom_type_hash; + +#define ASSIGN_TYPE_ID(clazz) \ + template<> \ + struct custom_type_hash \ + : std::integral_constant>>>{#clazz}> {} + +template +struct entt::type_hash { + static constexpr entt::id_type value() ENTT_NOEXCEPT { + return custom_type_hash::value; + } +}; + +struct position { + int x; + int y; +}; + +struct velocity { + double dx; + double dy; +}; + +struct userdata { + entt::meta_ctx ctx; + entt::meta_any any; +}; + +ASSIGN_TYPE_ID(void); +ASSIGN_TYPE_ID(std::size_t); +ASSIGN_TYPE_ID(position); +ASSIGN_TYPE_ID(velocity); +ASSIGN_TYPE_ID(double); +ASSIGN_TYPE_ID(int); + +#endif diff --git a/modules/entt/test/lib/registry/lib.cpp b/modules/entt/test/lib/registry/lib.cpp new file mode 100644 index 0000000..49028fc --- /dev/null +++ b/modules/entt/test/lib/registry/lib.cpp @@ -0,0 +1,19 @@ +#include +#include +#include "types.h" + +ENTT_API void update_position(entt::registry ®istry) { + registry.view().each([](auto &pos, auto &vel) { + pos.x += static_cast(16 * vel.dx); + pos.y += static_cast(16 * vel.dy); + }); +} + +ENTT_API void emplace_velocity(entt::registry ®istry) { + // forces the creation of the pool for the velocity component + static_cast(registry.storage()); + + for(auto entity: registry.view()) { + registry.emplace(entity, 1., 1.); + } +} diff --git a/modules/entt/test/lib/registry/main.cpp b/modules/entt/test/lib/registry/main.cpp new file mode 100644 index 0000000..afd2e8d --- /dev/null +++ b/modules/entt/test/lib/registry/main.cpp @@ -0,0 +1,27 @@ +#include +#include +#include +#include +#include "types.h" + +ENTT_API void update_position(entt::registry &); +ENTT_API void emplace_velocity(entt::registry &); + +TEST(Lib, Registry) { + entt::registry registry; + + for(auto i = 0; i < 3; ++i) { + const auto entity = registry.create(); + registry.emplace(entity, i, i); + } + + emplace_velocity(registry); + update_position(registry); + + ASSERT_EQ(registry.storage().size(), registry.storage().size()); + + registry.view().each([](auto entity, auto &position) { + ASSERT_EQ(position.x, static_cast(entt::to_integral(entity) + 16)); + ASSERT_EQ(position.y, static_cast(entt::to_integral(entity) + 16)); + }); +} diff --git a/modules/entt/test/lib/registry/types.h b/modules/entt/test/lib/registry/types.h new file mode 100644 index 0000000..6f126a8 --- /dev/null +++ b/modules/entt/test/lib/registry/types.h @@ -0,0 +1,16 @@ +#ifndef ENTT_LIB_REGISTRY_TYPES_H +#define ENTT_LIB_REGISTRY_TYPES_H + +#include + +struct ENTT_API position { + int x; + int y; +}; + +struct ENTT_API velocity { + double dx; + double dy; +}; + +#endif diff --git a/modules/entt/test/lib/registry_plugin/main.cpp b/modules/entt/test/lib/registry_plugin/main.cpp new file mode 100644 index 0000000..dbc04ce --- /dev/null +++ b/modules/entt/test/lib/registry_plugin/main.cpp @@ -0,0 +1,31 @@ +#define CR_HOST + +#include +#include +#include +#include "types.h" + +TEST(Lib, Registry) { + entt::registry registry; + + for(auto i = 0; i < 3; ++i) { + registry.emplace(registry.create(), i, i); + } + + cr_plugin ctx; + cr_plugin_load(ctx, PLUGIN); + + ctx.userdata = ®istry; + cr_plugin_update(ctx); + + ASSERT_EQ(registry.storage().size(), registry.storage().size()); + ASSERT_EQ(registry.storage().size(), registry.size()); + + registry.view().each([](auto entity, auto &position) { + ASSERT_EQ(position.x, static_cast(entt::to_integral(entity) + 16u)); + ASSERT_EQ(position.y, static_cast(entt::to_integral(entity) + 16u)); + }); + + registry = {}; + cr_plugin_close(ctx); +} diff --git a/modules/entt/test/lib/registry_plugin/plugin.cpp b/modules/entt/test/lib/registry_plugin/plugin.cpp new file mode 100644 index 0000000..e9c7d9f --- /dev/null +++ b/modules/entt/test/lib/registry_plugin/plugin.cpp @@ -0,0 +1,30 @@ +#include +#include +#include "types.h" + +CR_EXPORT int cr_main(cr_plugin *ctx, cr_op operation) { + switch(operation) { + case CR_STEP: { + // forces things to break + auto ®istry = *static_cast(ctx->userdata); + + // forces the creation of the pool for the velocity component + static_cast(registry.storage()); + + const auto view = registry.view(); + registry.insert(view.begin(), view.end(), velocity{1., 1.}); + + registry.view().each([](position &pos, velocity &vel) { + pos.x += static_cast(16 * vel.dx); + pos.y += static_cast(16 * vel.dy); + }); + } break; + case CR_CLOSE: + case CR_LOAD: + case CR_UNLOAD: + // nothing to do here, this is only a test. + break; + } + + return 0; +} diff --git a/modules/entt/test/lib/registry_plugin/types.h b/modules/entt/test/lib/registry_plugin/types.h new file mode 100644 index 0000000..2e0c14e --- /dev/null +++ b/modules/entt/test/lib/registry_plugin/types.h @@ -0,0 +1,14 @@ +#ifndef ENTT_LIB_REGISTRY_PLUGIN_TYPES_H +#define ENTT_LIB_REGISTRY_PLUGIN_TYPES_H + +struct position { + int x; + int y; +}; + +struct velocity { + double dx; + double dy; +}; + +#endif diff --git a/modules/entt/test/lib/types.h b/modules/entt/test/lib/types.h deleted file mode 100644 index f26b100..0000000 --- a/modules/entt/test/lib/types.h +++ /dev/null @@ -1,22 +0,0 @@ -#include -#include - -struct test_emitter - : entt::emitter -{}; - -ENTT_NAMED_STRUCT(position, { - int x; - int y; -}) - -ENTT_NAMED_STRUCT(velocity, { - int dx; - int dy; -}) - -ENTT_NAMED_STRUCT(an_event, { - int payload; -}) - -ENTT_NAMED_STRUCT(another_event, {}) diff --git a/modules/entt/test/mod/mod.cpp b/modules/entt/test/mod/mod.cpp deleted file mode 100644 index fe10470..0000000 --- a/modules/entt/test/mod/mod.cpp +++ /dev/null @@ -1,414 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -template -struct tag { using type = Type; }; - -struct position { - double x; - double y; -}; - -struct renderable {}; - -struct duktape_runtime { - std::map components; -}; - -template -duk_ret_t set(duk_context *ctx, entt::registry ®istry) { - const auto entity = duk_require_uint(ctx, 0); - - if constexpr(std::is_same_v) { - const auto x = duk_require_number(ctx, 2); - const auto y = duk_require_number(ctx, 3); - registry.assign_or_replace(entity, x, y); - } else if constexpr(std::is_same_v) { - const auto type = duk_require_uint(ctx, 1); - - duk_dup(ctx, 2); - - if(!registry.has(entity)) { - registry.assign(entity).components[type] = duk_json_encode(ctx, -1); - } else { - registry.get(entity).components[type] = duk_json_encode(ctx, -1); - } - - duk_pop(ctx); - } else { - registry.assign_or_replace(entity); - } - - return 0; -} - -template -duk_ret_t unset(duk_context *ctx, entt::registry ®istry) { - const auto entity = duk_require_uint(ctx, 0); - - if constexpr(std::is_same_v) { - const auto type = duk_require_uint(ctx, 1); - - auto &components = registry.get(entity).components; - assert(components.find(type) != components.cend()); - components.erase(type); - - if(components.empty()) { - registry.remove(entity); - } - } else { - registry.remove(entity); - } - - return 0; -} - -template -duk_ret_t has(duk_context *ctx, entt::registry ®istry) { - const auto entity = duk_require_uint(ctx, 0); - - if constexpr(std::is_same_v) { - duk_push_boolean(ctx, registry.has(entity)); - - if(registry.has(entity)) { - const auto type = duk_require_uint(ctx, 1); - const auto &components = registry.get(entity).components; - duk_push_boolean(ctx, components.find(type) != components.cend()); - } else { - duk_push_false(ctx); - } - } else { - duk_push_boolean(ctx, registry.has(entity)); - } - - return 1; -} - -template -duk_ret_t get(duk_context *ctx, entt::registry ®istry) { - [[maybe_unused]] const auto entity = duk_require_uint(ctx, 0); - - if constexpr(std::is_same_v) { - const auto &pos = registry.get(entity); - - const auto idx = duk_push_object(ctx); - - duk_push_string(ctx, "x"); - duk_push_number(ctx, pos.x); - duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE); - - duk_push_string(ctx, "y"); - duk_push_number(ctx, pos.y); - duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE); - } if constexpr(std::is_same_v) { - const auto type = duk_require_uint(ctx, 1); - - auto &runtime = registry.get(entity); - assert(runtime.components.find(type) != runtime.components.cend()); - - duk_push_string(ctx, runtime.components[type].c_str()); - duk_json_decode(ctx, -1); - } else { - assert(registry.has(entity)); - duk_push_object(ctx); - } - - return 1; -} - -class duktape_registry { - // I'm pretty sure I won't have more than 99 components in the example - static constexpr entt::registry::component_type udef = 100; - - struct func_map { - using func_type = duk_ret_t(*)(duk_context *, entt::registry &); - - func_type set; - func_type unset; - func_type has; - func_type get; - }; - - template - void reg() { - ((func[registry.type()] = { - &::set, - &::unset, - &::has, - &::get - }), ...); - } - - static duktape_registry & instance(duk_context *ctx) { - duk_push_this(ctx); - - duk_push_string(ctx, DUK_HIDDEN_SYMBOL("dreg")); - duk_get_prop(ctx, -2); - auto &dreg = *static_cast(duk_require_pointer(ctx, -1)); - duk_pop_2(ctx); - - return dreg; - } - - template - static duk_ret_t invoke(duk_context *ctx) { - auto &dreg = instance(ctx); - auto &func = dreg.func; - auto ®istry = dreg.registry; - auto type = duk_require_uint(ctx, 1); - - if(type >= udef) { - type = registry.type(); - } - - assert(func.find(type) != func.cend()); - - return (func[type].*Op)(ctx, registry); - } - -public: - duktape_registry(entt::registry &ref) - : registry{ref} - { - reg(); - } - - static duk_ret_t identifier(duk_context *ctx) { - static auto next = udef; - duk_push_uint(ctx, next++); - return 1; - } - - static duk_ret_t create(duk_context *ctx) { - auto &dreg = instance(ctx); - duk_push_uint(ctx, dreg.registry.create()); - return 1; - } - - static duk_ret_t set(duk_context *ctx) { - return invoke<&func_map::set>(ctx); - } - - static duk_ret_t unset(duk_context *ctx) { - return invoke<&func_map::unset>(ctx); - } - - static duk_ret_t has(duk_context *ctx) { - return invoke<&func_map::has>(ctx); - } - - static duk_ret_t get(duk_context *ctx) { - return invoke<&func_map::get>(ctx); - } - - static duk_ret_t entities(duk_context *ctx) { - const duk_idx_t nargs = duk_get_top(ctx); - auto &dreg = instance(ctx); - duk_uarridx_t pos = 0; - - duk_push_array(ctx); - - std::vector components; - std::vector runtime; - - for(duk_idx_t arg = 0; arg < nargs; arg++) { - auto type = duk_require_uint(ctx, arg); - - if(type < udef) { - components.push_back(type); - } else { - if(runtime.empty()) { - components.push_back(dreg.registry.type()); - } - - runtime.push_back(type); - } - } - - auto view = dreg.registry.runtime_view(components.cbegin(), components.cend()); - - for(const auto entity: view) { - if(runtime.empty()) { - duk_push_uint(ctx, entity); - duk_put_prop_index(ctx, -2, pos++); - } else { - const auto &others = dreg.registry.get(entity).components; - const auto match = std::all_of(runtime.cbegin(), runtime.cend(), [&others](const auto type) { - return others.find(type) != others.cend(); - }); - - if(match) { - duk_push_uint(ctx, entity); - duk_put_prop_index(ctx, -2, pos++); - } - } - } - - return 1; - } - -private: - std::map func; - entt::registry ®istry; -}; - -const duk_function_list_entry js_duktape_registry_methods[] = { - { "identifier", &duktape_registry::identifier, 0 }, - { "create", &duktape_registry::create, 0 }, - { "set", &duktape_registry::set, DUK_VARARGS }, - { "unset", &duktape_registry::unset, 2 }, - { "has", &duktape_registry::has, 2 }, - { "get", &duktape_registry::get, 2 }, - { "entities", &duktape_registry::entities, DUK_VARARGS }, - { nullptr, nullptr, 0 } -}; - -void export_types(duk_context *context, entt::registry ®istry) { - auto export_type = [](auto *ctx, auto ®, auto idx, auto type, const auto *name) { - duk_push_string(ctx, name); - duk_push_uint(ctx, reg.template type()); - duk_def_prop(ctx, idx, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_CLEAR_WRITABLE); - }; - - auto idx = duk_push_object(context); - - export_type(context, registry, idx, tag{}, "position"); - export_type(context, registry, idx, tag{}, "renderable"); - - duk_put_global_string(context, "Types"); -} - -void export_duktape_registry(duk_context *ctx, duktape_registry &dreg) { - auto idx = duk_push_object(ctx); - - duk_push_string(ctx, DUK_HIDDEN_SYMBOL("dreg")); - duk_push_pointer(ctx, &dreg); - duk_put_prop(ctx, idx); - - duk_put_function_list(ctx, idx, js_duktape_registry_methods); - duk_put_global_string(ctx, "Registry"); -} - -TEST(Mod, Duktape) { - entt::registry registry; - duktape_registry dreg{registry}; - duk_context *ctx = duk_create_heap_default(); - - if(!ctx) { - FAIL(); - } - - export_types(ctx, registry); - export_duktape_registry(ctx, dreg); - - const char *s0 = "" - "Types[\"PLAYING_CHARACTER\"] = Registry.identifier();" - "Types[\"VELOCITY\"] = Registry.identifier();" - ""; - - if(duk_peval_string(ctx, s0)) { - FAIL(); - } - - const auto e0 = registry.create(); - registry.assign(e0, 0., 0.); - registry.assign(e0); - - const auto e1 = registry.create(); - registry.assign(e1, 0., 0.); - - const char *s1 = "" - "Registry.entities(Types.position, Types.renderable).forEach(function(entity) {" - "Registry.set(entity, Types.position, 100., 100.);" - "});" - "var entity = Registry.create();" - "Registry.set(entity, Types.position, 100., 100.);" - "Registry.set(entity, Types.renderable);" - ""; - - if(duk_peval_string(ctx, s1)) { - FAIL(); - } - - ASSERT_EQ(registry.view().size(), 0u); - ASSERT_EQ(registry.view().size(), 3u); - ASSERT_EQ(registry.view().size(), 2u); - - registry.view().each([®istry](auto entity, const auto &position) { - ASSERT_FALSE(registry.has(entity)); - - if(registry.has(entity)) { - ASSERT_EQ(position.x, 100.); - ASSERT_EQ(position.y, 100.); - } else { - ASSERT_EQ(position.x, 0.); - ASSERT_EQ(position.y, 0.); - } - }); - - const char *s2 = "" - "Registry.entities(Types.position).forEach(function(entity) {" - "if(!Registry.has(entity, Types.renderable)) {" - "Registry.set(entity, Types.VELOCITY, { \"dx\": -100., \"dy\": -100. });" - "Registry.set(entity, Types.PLAYING_CHARACTER, {});" - "}" - "});" - ""; - - if(duk_peval_string(ctx, s2)) { - FAIL(); - } - - ASSERT_EQ(registry.view().size(), 1u); - ASSERT_EQ(registry.view().size(), 3u); - ASSERT_EQ(registry.view().size(), 2u); - - registry.view().each([](const duktape_runtime &runtime) { - ASSERT_EQ(runtime.components.size(), 2u); - }); - - const char *s3 = "" - "Registry.entities(Types.position, Types.renderable, Types.VELOCITY, Types.PLAYING_CHARACTER).forEach(function(entity) {" - "var velocity = Registry.get(entity, Types.VELOCITY);" - "Registry.set(entity, Types.position, velocity.dx, velocity.dy)" - "});" - ""; - - if(duk_peval_string(ctx, s3)) { - FAIL(); - } - - ASSERT_EQ(registry.view().size(), 1u); - ASSERT_EQ(registry.view().size(), 3u); - ASSERT_EQ(registry.view().size(), 2u); - - registry.view().each([](const position &position, auto &&...) { - ASSERT_EQ(position.x, -100.); - ASSERT_EQ(position.y, -100.); - }); - - const char *s4 = "" - "Registry.entities(Types.VELOCITY, Types.PLAYING_CHARACTER).forEach(function(entity) {" - "Registry.unset(entity, Types.VELOCITY);" - "Registry.unset(entity, Types.PLAYING_CHARACTER);" - "});" - "Registry.entities(Types.position).forEach(function(entity) {" - "Registry.unset(entity, Types.position);" - "});" - ""; - - if(duk_peval_string(ctx, s4)) { - FAIL(); - } - - ASSERT_EQ(registry.view().size(), 0u); - ASSERT_EQ(registry.view().size(), 0u); - ASSERT_EQ(registry.view().size(), 2u); - - duk_destroy_heap(ctx); -} diff --git a/modules/entt/test/snapshot/snapshot.cpp b/modules/entt/test/snapshot/snapshot.cpp index 48e549b..ac1d6ee 100644 --- a/modules/entt/test/snapshot/snapshot.cpp +++ b/modules/entt/test/snapshot/snapshot.cpp @@ -1,9 +1,10 @@ -#include #include #include +#include #include +#include #include -#include +#include struct position { float x; @@ -21,39 +22,41 @@ struct relationship { template void serialize(Archive &archive, position &position) { - archive(position.x, position.y); + archive(position.x, position.y); } template void serialize(Archive &archive, timer &timer) { - archive(timer.duration); + archive(timer.duration); } template void serialize(Archive &archive, relationship &relationship) { - archive(relationship.parent); + archive(relationship.parent); } TEST(Snapshot, Full) { + using namespace entt::literals; + std::stringstream storage; entt::registry source; entt::registry destination; auto e0 = source.create(); - source.assign(e0, 16.f, 16.f); + source.emplace(e0, 16.f, 16.f); source.destroy(source.create()); auto e1 = source.create(); - source.assign(e1, .8f, .0f); - source.assign(e1, e0); + source.emplace(e1, .8f, .0f); + source.emplace(e1, e0); auto e2 = source.create(); auto e3 = source.create(); - source.assign(e3, 1000, 100); - source.assign>(e3); + source.emplace(e3, 1000, 100); + source.emplace>(e3); source.destroy(e2); auto v2 = source.current(e2); @@ -61,37 +64,37 @@ TEST(Snapshot, Full) { { // output finishes flushing its contents when it goes out of scope cereal::JSONOutputArchive output{storage}; - source.snapshot().entities(output).destroyed(output) - .component>(output); + entt::snapshot{source}.entities(output).component>(output); } cereal::JSONInputArchive input{storage}; - destination.loader().entities(input).destroyed(input) - .component>(input); + entt::snapshot_loader{destination}.entities(input).component>(input); ASSERT_TRUE(destination.valid(e0)); - ASSERT_TRUE(destination.has(e0)); + ASSERT_TRUE(destination.all_of(e0)); ASSERT_EQ(destination.get(e0).x, 16.f); ASSERT_EQ(destination.get(e0).y, 16.f); ASSERT_TRUE(destination.valid(e1)); - ASSERT_TRUE(destination.has(e1)); + ASSERT_TRUE(destination.all_of(e1)); ASSERT_EQ(destination.get(e1).x, .8f); ASSERT_EQ(destination.get(e1).y, .0f); - ASSERT_TRUE(destination.has(e1)); + ASSERT_TRUE(destination.all_of(e1)); ASSERT_EQ(destination.get(e1).parent, e0); ASSERT_FALSE(destination.valid(e2)); ASSERT_EQ(destination.current(e2), v2); ASSERT_TRUE(destination.valid(e3)); - ASSERT_TRUE(destination.has(e3)); - ASSERT_TRUE(destination.has>(e3)); + ASSERT_TRUE(destination.all_of(e3)); + ASSERT_TRUE(destination.all_of>(e3)); ASSERT_EQ(destination.get(e3).duration, 1000); ASSERT_EQ(destination.get(e3).elapsed, 0); } TEST(Snapshot, Continuous) { + using namespace entt::literals; + std::stringstream storage; entt::registry source; @@ -107,80 +110,80 @@ TEST(Snapshot, Continuous) { } auto e0 = source.create(); - source.assign(e0, 0.f, 0.f); - source.assign(e0, e0); + source.emplace(e0, 0.f, 0.f); + source.emplace(e0, e0); auto e1 = source.create(); - source.assign(e1, 1.f, 1.f); - source.assign(e1, e0); + source.emplace(e1, 1.f, 1.f); + source.emplace(e1, e0); auto e2 = source.create(); - source.assign(e2, .2f, .2f); - source.assign(e2, e0); + source.emplace(e2, .2f, .2f); + source.emplace(e2, e0); auto e3 = source.create(); - source.assign(e3, 1000, 1000); - source.assign(e3, e2); - source.assign>(e3); + source.emplace(e3, 1000, 1000); + source.emplace(e3, e2); + source.emplace>(e3); { // output finishes flushing its contents when it goes out of scope cereal::JSONOutputArchive output{storage}; - source.snapshot().entities(output).component>(output); + entt::snapshot{source}.entities(output).component>(output); } cereal::JSONInputArchive input{storage}; entt::continuous_loader loader{destination}; loader.entities(input) - .component(input, &relationship::parent) - .component>(input); + .component(input, &relationship::parent) + .component>(input); ASSERT_FALSE(destination.valid(e0)); - ASSERT_TRUE(loader.has(e0)); + ASSERT_TRUE(loader.contains(e0)); auto l0 = loader.map(e0); ASSERT_TRUE(destination.valid(l0)); - ASSERT_TRUE(destination.has(l0)); + ASSERT_TRUE(destination.all_of(l0)); ASSERT_EQ(destination.get(l0).x, 0.f); ASSERT_EQ(destination.get(l0).y, 0.f); - ASSERT_TRUE(destination.has(l0)); + ASSERT_TRUE(destination.all_of(l0)); ASSERT_EQ(destination.get(l0).parent, l0); ASSERT_FALSE(destination.valid(e1)); - ASSERT_TRUE(loader.has(e1)); + ASSERT_TRUE(loader.contains(e1)); auto l1 = loader.map(e1); ASSERT_TRUE(destination.valid(l1)); - ASSERT_TRUE(destination.has(l1)); + ASSERT_TRUE(destination.all_of(l1)); ASSERT_EQ(destination.get(l1).x, 1.f); ASSERT_EQ(destination.get(l1).y, 1.f); - ASSERT_TRUE(destination.has(l1)); + ASSERT_TRUE(destination.all_of(l1)); ASSERT_EQ(destination.get(l1).parent, l0); ASSERT_FALSE(destination.valid(e2)); - ASSERT_TRUE(loader.has(e2)); + ASSERT_TRUE(loader.contains(e2)); auto l2 = loader.map(e2); ASSERT_TRUE(destination.valid(l2)); - ASSERT_TRUE(destination.has(l2)); + ASSERT_TRUE(destination.all_of(l2)); ASSERT_EQ(destination.get(l2).x, .2f); ASSERT_EQ(destination.get(l2).y, .2f); - ASSERT_TRUE(destination.has(l2)); + ASSERT_TRUE(destination.all_of(l2)); ASSERT_EQ(destination.get(l2).parent, l0); ASSERT_FALSE(destination.valid(e3)); - ASSERT_TRUE(loader.has(e3)); + ASSERT_TRUE(loader.contains(e3)); auto l3 = loader.map(e3); ASSERT_TRUE(destination.valid(l3)); - ASSERT_TRUE(destination.has(l3)); + ASSERT_TRUE(destination.all_of(l3)); ASSERT_EQ(destination.get(l3).duration, 1000); ASSERT_EQ(destination.get(l3).elapsed, 0); - ASSERT_TRUE(destination.has(l3)); + ASSERT_TRUE(destination.all_of(l3)); ASSERT_EQ(destination.get(l3).parent, l2); - ASSERT_TRUE(destination.has>(l3)); + ASSERT_TRUE(destination.all_of>(l3)); }