Răsfoiți Sursa

Implemented Entity Component System for all objects, using Entt
Converted all game objects to components, added component creation throughout System Scenes
Added a map file that contains the scene as components
Added Entt ECS library dependencies

Paul A 3 ani în urmă
părinte
comite
ae8d89d1c8
100 a modificat fișierele cu 21156 adăugiri și 380 ștergeri
  1. 69 0
      Dependencies/include/entt/config/config.h
  2. 8 0
      Dependencies/include/entt/config/version.h
  3. 959 0
      Dependencies/include/entt/container/dense_hash_map.hpp
  4. 849 0
      Dependencies/include/entt/container/dense_hash_set.hpp
  5. 25 0
      Dependencies/include/entt/container/fwd.hpp
  6. 137 0
      Dependencies/include/entt/core/algorithm.hpp
  7. 494 0
      Dependencies/include/entt/core/any.hpp
  8. 30 0
      Dependencies/include/entt/core/attribute.h
  9. 269 0
      Dependencies/include/entt/core/compressed_pair.hpp
  10. 112 0
      Dependencies/include/entt/core/enum.hpp
  11. 32 0
      Dependencies/include/entt/core/family.hpp
  12. 20 0
      Dependencies/include/entt/core/fwd.hpp
  13. 314 0
      Dependencies/include/entt/core/hashed_string.hpp
  14. 59 0
      Dependencies/include/entt/core/ident.hpp
  15. 116 0
      Dependencies/include/entt/core/iterator.hpp
  16. 108 0
      Dependencies/include/entt/core/memory.hpp
  17. 56 0
      Dependencies/include/entt/core/monostate.hpp
  18. 29 0
      Dependencies/include/entt/core/tuple.hpp
  19. 267 0
      Dependencies/include/entt/core/type_info.hpp
  20. 678 0
      Dependencies/include/entt/core/type_traits.hpp
  21. 101 0
      Dependencies/include/entt/core/utility.hpp
  22. 37 0
      Dependencies/include/entt/entity/component.hpp
  23. 339 0
      Dependencies/include/entt/entity/entity.hpp
  24. 117 0
      Dependencies/include/entt/entity/fwd.hpp
  25. 904 0
      Dependencies/include/entt/entity/group.hpp
  26. 340 0
      Dependencies/include/entt/entity/handle.hpp
  27. 156 0
      Dependencies/include/entt/entity/helper.hpp
  28. 436 0
      Dependencies/include/entt/entity/observer.hpp
  29. 487 0
      Dependencies/include/entt/entity/organizer.hpp
  30. 1514 0
      Dependencies/include/entt/entity/registry.hpp
  31. 267 0
      Dependencies/include/entt/entity/runtime_view.hpp
  32. 562 0
      Dependencies/include/entt/entity/snapshot.hpp
  33. 947 0
      Dependencies/include/entt/entity/sparse_set.hpp
  34. 1091 0
      Dependencies/include/entt/entity/storage.hpp
  35. 52 0
      Dependencies/include/entt/entity/utility.hpp
  36. 848 0
      Dependencies/include/entt/entity/view.hpp
  37. 57 0
      Dependencies/include/entt/entt.hpp
  38. 7 0
      Dependencies/include/entt/fwd.hpp
  39. 106 0
      Dependencies/include/entt/locator/locator.hpp
  40. 35 0
      Dependencies/include/entt/meta/adl_pointer.hpp
  41. 274 0
      Dependencies/include/entt/meta/container.hpp
  42. 57 0
      Dependencies/include/entt/meta/ctx.hpp
  43. 652 0
      Dependencies/include/entt/meta/factory.hpp
  44. 24 0
      Dependencies/include/entt/meta/fwd.hpp
  45. 1869 0
      Dependencies/include/entt/meta/meta.hpp
  46. 235 0
      Dependencies/include/entt/meta/node.hpp
  47. 48 0
      Dependencies/include/entt/meta/pointer.hpp
  48. 66 0
      Dependencies/include/entt/meta/policy.hpp
  49. 124 0
      Dependencies/include/entt/meta/range.hpp
  50. 63 0
      Dependencies/include/entt/meta/resolve.hpp
  51. 27 0
      Dependencies/include/entt/meta/template.hpp
  52. 55 0
      Dependencies/include/entt/meta/type_traits.hpp
  53. 396 0
      Dependencies/include/entt/meta/utility.hpp
  54. 67 0
      Dependencies/include/entt/platform/android-ndk-r17.hpp
  55. 20 0
      Dependencies/include/entt/poly/fwd.hpp
  56. 313 0
      Dependencies/include/entt/poly/poly.hpp
  57. 332 0
      Dependencies/include/entt/process/process.hpp
  58. 290 0
      Dependencies/include/entt/process/scheduler.hpp
  59. 280 0
      Dependencies/include/entt/resource/cache.hpp
  60. 17 0
      Dependencies/include/entt/resource/fwd.hpp
  61. 183 0
      Dependencies/include/entt/resource/handle.hpp
  62. 61 0
      Dependencies/include/entt/resource/loader.hpp
  63. 339 0
      Dependencies/include/entt/signal/delegate.hpp
  64. 264 0
      Dependencies/include/entt/signal/dispatcher.hpp
  65. 316 0
      Dependencies/include/entt/signal/emitter.hpp
  66. 28 0
      Dependencies/include/entt/signal/fwd.hpp
  67. 611 0
      Dependencies/include/entt/signal/sigh.hpp
  68. 6 0
      Dependencies/include/imgui/backends/imgui_impl_opengl3_loader.h
  69. 3 0
      Dependencies/include/imgui/imgui.cpp
  70. 186 117
      Praxis3D/Data/Maps/componentTest.pmap
  71. 339 0
      Praxis3D/Data/Maps/componentTest2.pmap
  72. 1 1
      Praxis3D/Data/Scripts/Window_controls.lua
  73. 2 2
      Praxis3D/Data/config.ini
  74. 5 1
      Praxis3D/Praxis3D.vcxproj
  75. 13 4
      Praxis3D/Praxis3D.vcxproj.filters
  76. 3 3
      Praxis3D/Source/CameraComponent.h
  77. 4 0
      Praxis3D/Source/ChangeController.h
  78. 4 0
      Praxis3D/Source/Clock.h
  79. 3 0
      Praxis3D/Source/CommonDefinitions.h
  80. 6 0
      Praxis3D/Source/Config.cpp
  81. 13 0
      Praxis3D/Source/Config.h
  82. 4 0
      Praxis3D/Source/Engine.h
  83. 14 0
      Praxis3D/Source/EntityViewDefinitions.h
  84. 4 4
      Praxis3D/Source/GUIDataManager.h
  85. 27 27
      Praxis3D/Source/GUIObject.h
  86. 64 15
      Praxis3D/Source/GUIScene.cpp
  87. 1 0
      Praxis3D/Source/GUIScene.h
  88. 11 9
      Praxis3D/Source/GUISequenceComponent.h
  89. 3 3
      Praxis3D/Source/GameObject.h
  90. 115 0
      Praxis3D/Source/GameObjectComponent.h
  91. 48 3
      Praxis3D/Source/GeometryPass.h
  92. 2 2
      Praxis3D/Source/GraphicsDataSets.h
  93. 10 10
      Praxis3D/Source/GraphicsObject.h
  94. 13 0
      Praxis3D/Source/InheritanceObjects.h
  95. 23 20
      Praxis3D/Source/LightComponent.h
  96. 10 0
      Praxis3D/Source/LoaderBase.h
  97. 22 12
      Praxis3D/Source/LuaComponent.h
  98. 0 1
      Praxis3D/Source/LuaScript.h
  99. 151 144
      Praxis3D/Source/ModelComponent.h
  100. 1 2
      Praxis3D/Source/ModelLoader.cpp

+ 69 - 0
Dependencies/include/entt/config/config.h

@@ -0,0 +1,69 @@
+#ifndef ENTT_CONFIG_CONFIG_H
+#define ENTT_CONFIG_CONFIG_H
+
+#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION)
+#    define ENTT_NOEXCEPT noexcept
+#    define ENTT_THROW throw
+#    define ENTT_TRY try
+#    define ENTT_CATCH catch(...)
+#else
+#    define ENTT_NOEXCEPT
+#    define ENTT_THROW
+#    define ENTT_TRY if(true)
+#    define ENTT_CATCH if(false)
+#endif
+
+#if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606L
+#    include <new>
+#    define ENTT_LAUNDER(expr) std::launder(expr)
+#else
+#    define ENTT_LAUNDER(expr) expr
+#endif
+
+#ifndef ENTT_USE_ATOMIC
+#    define ENTT_MAYBE_ATOMIC(Type) Type
+#else
+#    include <atomic>
+#    define ENTT_MAYBE_ATOMIC(Type) std::atomic<Type>
+#endif
+
+#ifndef ENTT_ID_TYPE
+#    include <cstdint>
+#    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 <cassert>
+#    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_STANDARD_CPP
+#    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
+
+#endif

+ 8 - 0
Dependencies/include/entt/config/version.h

@@ -0,0 +1,8 @@
+#ifndef ENTT_CONFIG_VERSION_H
+#define ENTT_CONFIG_VERSION_H
+
+#define ENTT_VERSION_MAJOR 3
+#define ENTT_VERSION_MINOR 10
+#define ENTT_VERSION_PATCH 0
+
+#endif

+ 959 - 0
Dependencies/include/entt/container/dense_hash_map.hpp

@@ -0,0 +1,959 @@
+#ifndef ENTT_CONTAINER_DENSE_HASH_MAP_HPP
+#define ENTT_CONTAINER_DENSE_HASH_MAP_HPP
+
+#include <algorithm>
+#include <cmath>
+#include <cstddef>
+#include <functional>
+#include <iterator>
+#include <limits>
+#include <memory>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <vector>
+#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<typename Key, typename Type>
+struct dense_hash_map_node final {
+    template<typename... Args>
+    dense_hash_map_node(const std::size_t pos, Args &&...args)
+        : next{pos},
+          element{std::forward<Args>(args)...} {}
+
+    std::size_t next;
+    std::pair<Key, Type> element;
+};
+
+template<typename It>
+class dense_hash_map_iterator {
+    friend dense_hash_map_iterator<const std::remove_pointer_t<It> *>;
+
+    using iterator_traits = std::iterator_traits<decltype(std::addressof(std::declval<It>()->element))>;
+
+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;
+
+    dense_hash_map_iterator() ENTT_NOEXCEPT = default;
+
+    dense_hash_map_iterator(const It iter) ENTT_NOEXCEPT
+        : it{iter} {}
+
+    template<bool Const = std::is_const_v<std::remove_pointer_t<It>>, typename = std::enable_if_t<Const>>
+    dense_hash_map_iterator(const dense_hash_map_iterator<std::remove_const_t<std::remove_pointer_t<It>> *> &other)
+        : it{other.it} {}
+
+    dense_hash_map_iterator &operator++() ENTT_NOEXCEPT {
+        return ++it, *this;
+    }
+
+    dense_hash_map_iterator operator++(int) ENTT_NOEXCEPT {
+        dense_hash_map_iterator orig = *this;
+        return ++(*this), orig;
+    }
+
+    dense_hash_map_iterator &operator--() ENTT_NOEXCEPT {
+        return --it, *this;
+    }
+
+    dense_hash_map_iterator operator--(int) ENTT_NOEXCEPT {
+        dense_hash_map_iterator orig = *this;
+        return operator--(), orig;
+    }
+
+    dense_hash_map_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT {
+        it += value;
+        return *this;
+    }
+
+    dense_hash_map_iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
+        dense_hash_map_iterator copy = *this;
+        return (copy += value);
+    }
+
+    dense_hash_map_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT {
+        return (*this += -value);
+    }
+
+    dense_hash_map_iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
+        return (*this + -value);
+    }
+
+    [[nodiscard]] reference operator[](const difference_type value) const {
+        return it[value].element;
+    }
+
+    [[nodiscard]] pointer operator->() const {
+        return std::addressof(it->element);
+    }
+
+    [[nodiscard]] reference operator*() const {
+        return *operator->();
+    }
+
+    template<typename ILhs, typename IRhs>
+    friend auto operator-(const dense_hash_map_iterator<ILhs> &, const dense_hash_map_iterator<IRhs> &) ENTT_NOEXCEPT;
+
+    template<typename ILhs, typename IRhs>
+    friend bool operator==(const dense_hash_map_iterator<ILhs> &, const dense_hash_map_iterator<IRhs> &) ENTT_NOEXCEPT;
+
+    template<typename ILhs, typename IRhs>
+    friend bool operator<(const dense_hash_map_iterator<ILhs> &, const dense_hash_map_iterator<IRhs> &) ENTT_NOEXCEPT;
+
+private:
+    It it;
+};
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] auto operator-(const dense_hash_map_iterator<ILhs> &lhs, const dense_hash_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return lhs.it - rhs.it;
+}
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator==(const dense_hash_map_iterator<ILhs> &lhs, const dense_hash_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return lhs.it == rhs.it;
+}
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator!=(const dense_hash_map_iterator<ILhs> &lhs, const dense_hash_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return !(lhs == rhs);
+}
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator<(const dense_hash_map_iterator<ILhs> &lhs, const dense_hash_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return lhs.it < rhs.it;
+}
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator>(const dense_hash_map_iterator<ILhs> &lhs, const dense_hash_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return rhs < lhs;
+}
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator<=(const dense_hash_map_iterator<ILhs> &lhs, const dense_hash_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return !(lhs > rhs);
+}
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator>=(const dense_hash_map_iterator<ILhs> &lhs, const dense_hash_map_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return !(lhs < rhs);
+}
+
+template<typename It>
+class dense_hash_map_local_iterator {
+    friend dense_hash_map_local_iterator<const std::remove_pointer_t<It> *>;
+
+    using iterator_traits = std::iterator_traits<decltype(std::addressof(std::declval<It>()->element))>;
+
+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::forward_iterator_tag;
+
+    dense_hash_map_local_iterator() ENTT_NOEXCEPT = default;
+
+    dense_hash_map_local_iterator(It iter, const std::size_t pos) ENTT_NOEXCEPT
+        : it{iter},
+          offset{pos} {}
+
+    template<bool Const = std::is_const_v<std::remove_pointer_t<It>>, typename = std::enable_if_t<Const>>
+    dense_hash_map_local_iterator(const dense_hash_map_local_iterator<std::remove_const_t<std::remove_pointer_t<It>> *> &other)
+        : it{other.it},
+          offset{other.offset} {}
+
+    dense_hash_map_local_iterator &operator++() ENTT_NOEXCEPT {
+        return offset = it[offset].next, *this;
+    }
+
+    dense_hash_map_local_iterator operator++(int) ENTT_NOEXCEPT {
+        dense_hash_map_local_iterator orig = *this;
+        return ++(*this), orig;
+    }
+
+    [[nodiscard]] pointer operator->() const {
+        return std::addressof(it[offset].element);
+    }
+
+    [[nodiscard]] reference operator*() const {
+        return *operator->();
+    }
+
+    [[nodiscard]] std::size_t index() const ENTT_NOEXCEPT {
+        return offset;
+    }
+
+private:
+    It it;
+    std::size_t offset;
+};
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator==(const dense_hash_map_local_iterator<ILhs> &lhs, const dense_hash_map_local_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return lhs.index() == rhs.index();
+}
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator!=(const dense_hash_map_local_iterator<ILhs> &lhs, const dense_hash_map_local_iterator<IRhs> &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<typename Key, typename Type, typename Hash, typename KeyEqual, typename Allocator>
+class dense_hash_map final {
+    static constexpr float default_threshold = 0.875f;
+    static constexpr std::size_t minimum_capacity = 8u;
+
+    using allocator_traits = std::allocator_traits<Allocator>;
+    using alloc = typename allocator_traits::template rebind_alloc<std::pair<const Key, Type>>;
+    using alloc_traits = typename std::allocator_traits<alloc>;
+
+    using node_type = internal::dense_hash_map_node<const Key, Type>;
+    using sparse_container_type = std::vector<std::size_t, typename alloc_traits::template rebind_alloc<std::size_t>>;
+    using packed_container_type = std::vector<node_type, typename alloc_traits::template rebind_alloc<node_type>>;
+
+    [[nodiscard]] std::size_t hash_to_bucket(const std::size_t hash) const ENTT_NOEXCEPT {
+        return fast_mod(hash, bucket_count());
+    }
+
+    template<typename Other>
+    [[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() + it.index();
+            }
+        }
+
+        return end();
+    }
+
+    template<typename Other>
+    [[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() + it.index();
+            }
+        }
+
+        return cend();
+    }
+
+    template<typename... Args>
+    [[nodiscard]] auto get_or_emplace(const Key &key, Args &&...args) {
+        const auto hash = sparse.second()(key);
+        auto index = hash_to_bucket(hash);
+
+        if(auto it = constrained_find(key, index); it != end()) {
+            return std::make_pair(it, false);
+        }
+
+        if(const auto count = size() + 1u; count > (bucket_count() * max_load_factor())) {
+            rehash(bucket_count() * 2u);
+            index = hash_to_bucket(hash);
+        }
+
+        packed.first().emplace_back(sparse.first()[index], std::forward<Args>(args)...);
+        // update goes after emplace to enforce exception guarantees
+        sparse.first()[index] = size() - 1u;
+
+        return std::make_pair(--end(), true);
+    }
+
+    void move_and_pop(const std::size_t pos) {
+        if(const auto last = size() - 1u; pos != last) {
+            size_type *curr = sparse.first().data() + bucket(packed.first().back().element.first);
+            for(; *curr != last; curr = &packed.first()[*curr].next) {}
+            *curr = pos;
+
+            using node_allocator_traits = typename alloc_traits::template rebind_traits<decltype(node_type::element)>;
+            typename node_allocator_traits::allocator_type allocator = packed.first().get_allocator();
+            auto *ptr = std::addressof(packed.first()[pos].element);
+
+            std::destroy_at(ptr);
+            packed.first()[pos].next = packed.first().back().next;
+            // no exception guarantees when mapped type has a throwing move constructor (we're technically doomed)
+            node_allocator_traits::construct(allocator, ptr, std::move(packed.first().back().element));
+        }
+
+        packed.first().pop_back();
+    }
+
+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<const Key, Type>;
+    /*! @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 Random access iterator type. */
+    using iterator = internal::dense_hash_map_iterator<typename packed_container_type::pointer>;
+    /*! @brief Constant random access iterator type. */
+    using const_iterator = internal::dense_hash_map_iterator<typename packed_container_type::const_pointer>;
+    /*! @brief Forward iterator type. */
+    using local_iterator = internal::dense_hash_map_local_iterator<typename packed_container_type::pointer>;
+    /*! @brief Constant forward iterator type. */
+    using const_local_iterator = internal::dense_hash_map_local_iterator<typename packed_container_type::const_pointer>;
+
+    /*! @brief Default constructor. */
+    dense_hash_map()
+        : dense_hash_map(minimum_capacity) {}
+
+    /**
+     * @brief Constructs an empty container with a given allocator.
+     * @param allocator The allocator to use.
+     */
+    explicit dense_hash_map(const allocator_type &allocator)
+        : dense_hash_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_hash_map(const size_type bucket_count, const allocator_type &allocator)
+        : dense_hash_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_hash_map(const size_type bucket_count, const hasher &hash, const allocator_type &allocator)
+        : dense_hash_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_hash_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 Copy constructor.
+     * @param other The instance to copy from.
+     */
+    dense_hash_map(const dense_hash_map &other)
+        : dense_hash_map{other, alloc_traits::select_on_container_copy_construction(other.get_allocator())} {}
+
+    /**
+     * @brief Allocator-extended copy constructor.
+     * @param other The instance to copy from.
+     * @param allocator The allocator to use.
+     */
+    dense_hash_map(const dense_hash_map &other, const allocator_type &allocator)
+        : sparse{sparse_container_type{other.sparse.first(), allocator}, other.sparse.second()},
+          // cannot copy the container directly due to a nasty issue of apple clang :(
+          packed{packed_container_type{other.packed.first().begin(), other.packed.first().end(), allocator}, other.packed.second()},
+          threshold{other.threshold} {
+    }
+
+    /**
+     * @brief Default move constructor.
+     * @param other The instance to move from.
+     */
+    dense_hash_map(dense_hash_map &&other) ENTT_NOEXCEPT = default;
+
+    /**
+     * @brief Allocator-extended move constructor.
+     * @param other The instance to move from.
+     * @param allocator The allocator to use.
+     */
+    dense_hash_map(dense_hash_map &&other, const allocator_type &allocator) ENTT_NOEXCEPT
+        : sparse{sparse_container_type{std::move(other.sparse.first()), allocator}, std::move(other.sparse.second())},
+          // cannot move the container directly due to a nasty issue of apple clang :(
+          packed{packed_container_type{std::make_move_iterator(other.packed.first().begin()), std::make_move_iterator(other.packed.first().end()), allocator}, std::move(other.packed.second())},
+          threshold{other.threshold} {}
+
+    /*! @brief Default destructor. */
+    ~dense_hash_map() = default;
+
+    /**
+     * @brief Copy assignment operator.
+     * @param other The instance to copy from.
+     * @return This container.
+     */
+    dense_hash_map &operator=(const dense_hash_map &other) {
+        threshold = other.threshold;
+        sparse.first().clear();
+        packed.first().clear();
+        rehash(other.bucket_count());
+        packed.first().reserve(other.packed.first().size());
+        insert(other.cbegin(), other.cend());
+        return *this;
+    }
+
+    /**
+     * @brief Default move assignment operator.
+     * @param other The instance to move from.
+     * @return This container.
+     */
+    dense_hash_map &operator=(dense_hash_map &&other) ENTT_NOEXCEPT = 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().data();
+    }
+
+    /*! @copydoc cbegin */
+    [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT {
+        return cbegin();
+    }
+
+    /*! @copydoc begin */
+    [[nodiscard]] iterator begin() ENTT_NOEXCEPT {
+        return packed.first().data();
+    }
+
+    /**
+     * @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().data() + size();
+    }
+
+    /*! @copydoc cend */
+    [[nodiscard]] const_iterator end() const ENTT_NOEXCEPT {
+        return cend();
+    }
+
+    /*! @copydoc end */
+    [[nodiscard]] iterator end() ENTT_NOEXCEPT {
+        return packed.first().data() + size();
+    }
+
+    /**
+     * @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<iterator, bool> insert(const value_type &value) {
+        return emplace(value);
+    }
+
+    /*! @copydoc insert */
+    std::pair<iterator, bool> insert(value_type &&value) {
+        return emplace(std::move(value));
+    }
+
+    /**
+     * @copydoc insert
+     * @tparam Arg Type of the key-value pair to insert into the container.
+     */
+    template<typename Arg>
+    std::enable_if_t<std::is_constructible_v<value_type, Arg &&>, std::pair<iterator, bool>>
+    insert(Arg &&value) {
+        return emplace(std::forward<Arg>(value));
+    }
+
+    /**
+     * @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<typename It>
+    void insert(It first, It last) {
+        for(; first != last; ++first) {
+            emplace(*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<typename Arg>
+    std::pair<iterator, bool> insert_or_assign(const key_type &key, Arg &&value) {
+        auto result = try_emplace(key, std::forward<Arg>(value));
+
+        if(!result.second) {
+            result.first->second = std::forward<Arg>(value);
+        }
+
+        return result;
+    }
+
+    /*! @copydoc insert_or_assign */
+    template<typename Arg>
+    std::pair<iterator, bool> insert_or_assign(key_type &&key, Arg &&value) {
+        auto result = try_emplace(std::move(key), std::forward<Arg>(value));
+
+        if(!result.second) {
+            result.first->second = std::forward<Arg>(value);
+        }
+
+        return result;
+    }
+
+    /**
+     * @brief Constructs an element in-place, if the key does not exist.
+     * @tparam Args Types of arguments to forward to the constructor of the
+     * element.
+     * @param args Arguments to forward to the constructor of the element.
+     * @return A pair consisting of an iterator to the inserted element (or to
+     * the element that prevented the insertion) and a bool denoting whether the
+     * insertion took place.
+     */
+    template<typename... Args>
+    std::pair<iterator, bool> emplace(Args &&...args) {
+        if constexpr(sizeof...(Args) == 0u) {
+            return get_or_emplace(key_type{});
+        } else if constexpr(sizeof...(Args) == 1u) {
+            return get_or_emplace(args.first..., std::forward<Args>(args)...);
+        } else if constexpr(sizeof...(Args) == 2u) {
+            return get_or_emplace(std::get<0u>(std::tie(args...)), std::forward<Args>(args)...);
+        } else {
+            static_assert(sizeof...(Args) == 3u, "Invalid arguments");
+            return emplace(std::pair<key_type, mapped_type>{std::forward<Args>(args)...});
+        }
+    }
+
+    /**
+     * @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<typename... Args>
+    std::pair<iterator, bool> try_emplace(const key_type &key, Args &&...args) {
+        return get_or_emplace(key, std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(std::forward<Args>(args)...));
+    }
+
+    /*! @copydoc try_emplace */
+    template<typename... Args>
+    std::pair<iterator, bool> try_emplace(key_type &&key, Args &&...args) {
+        return get_or_emplace(key, std::piecewise_construct, std::forward_as_tuple(std::move(key)), std::forward_as_tuple(std::forward<Args>(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 dist = std::distance(cbegin(), pos);
+        erase(pos->first);
+        return begin() + dist;
+    }
+
+    /**
+     * @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 = std::distance(cbegin(), first);
+
+        for(auto rfirst = std::make_reverse_iterator(last), rlast = std::make_reverse_iterator(first); rfirst != rlast; ++rfirst) {
+            erase(rfirst->first);
+        }
+
+        return dist > static_cast<decltype(dist)>(size()) ? end() : (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() + bucket(key); *curr != std::numeric_limits<size_type>::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 true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * @brief Exchanges the contents with those of a given container.
+     * @param other Container to exchange the content with.
+     */
+    void swap(dense_hash_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]] Type &at(const key_type &key) {
+        auto it = find(key);
+        ENTT_ASSERT(it != end(), "Invalid key");
+        return it->second;
+    }
+
+    /*! @copydoc at */
+    [[nodiscard]] const 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]] Type &operator[](const key_type &key) {
+        return try_emplace(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]] Type &operator[](key_type &&key) {
+        return try_emplace(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, bucket(key));
+    }
+
+    /*! @copydoc find */
+    [[nodiscard]] const_iterator find(const key_type &key) const {
+        return constrained_find(key, 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<typename Other>
+    [[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, iterator>>
+    find(const Other &key) {
+        return constrained_find(key, bucket(key));
+    }
+
+    /*! @copydoc find */
+    template<typename Other>
+    [[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, const_iterator>>
+    find(const Other &key) const {
+        return constrained_find(key, 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<typename Other>
+    [[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, bool>>
+    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().data(), sparse.first()[index]};
+    }
+
+    /**
+     * @brief Returns an iterator to the beginning of a given bucket.
+     * @param index An index of a bucket to access.
+     * @return An iterator to the beginning of the given bucket.
+     */
+    [[nodiscard]] const_local_iterator begin(const size_type index) const {
+        return cbegin(index);
+    }
+
+    /**
+     * @brief Returns an iterator to the beginning of a given bucket.
+     * @param index An index of a bucket to access.
+     * @return An iterator to the beginning of the given bucket.
+     */
+    [[nodiscard]] local_iterator begin(const size_type index) {
+        return {packed.first().data(), sparse.first()[index]};
+    }
+
+    /**
+     * @brief Returns an iterator to the end of a given bucket.
+     * @param index An index of a bucket to access.
+     * @return An iterator to the end of the given bucket.
+     */
+    [[nodiscard]] const_local_iterator cend([[maybe_unused]] const size_type index) const {
+        return {packed.first().data(), std::numeric_limits<size_type>::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().data(), std::numeric_limits<size_type>::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<size_type>(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 hash_to_bucket(sparse.second()(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<float>(bucket_count());
+    }
+
+    /**
+     * @brief Returns the maximum average number of elements per bucket.
+     * @return The maximum average number of elements per bucket.
+     */
+    [[nodiscard]] float max_load_factor() const {
+        return threshold;
+    }
+
+    /**
+     * @brief Sets the desired maximum average number of elements per bucket.
+     * @param value A desired maximum average number of elements per bucket.
+     */
+    void max_load_factor(const float value) {
+        ENTT_ASSERT(value > 0.f, "Invalid load factor");
+        threshold = value;
+        rehash(0u);
+    }
+
+    /**
+     * @brief Reserves at least the specified number of buckets and regenerates
+     * the hash table.
+     * @param 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_type>(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<size_type>::max());
+
+            for(size_type pos{}, last = size(); pos < last; ++pos) {
+                const auto index = 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<size_type>(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_container_type, hasher> sparse;
+    compressed_pair<packed_container_type, key_equal> packed;
+    float threshold;
+};
+
+} // namespace entt
+
+#endif

+ 849 - 0
Dependencies/include/entt/container/dense_hash_set.hpp

@@ -0,0 +1,849 @@
+#ifndef ENTT_CONTAINER_DENSE_HASH_SET_HPP
+#define ENTT_CONTAINER_DENSE_HASH_SET_HPP
+
+#include <algorithm>
+#include <cmath>
+#include <cstddef>
+#include <functional>
+#include <iterator>
+#include <limits>
+#include <memory>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <vector>
+#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<typename Type>
+struct dense_hash_set_node final {
+    template<typename... Args>
+    dense_hash_set_node(const std::size_t pos, Args &&...args)
+        : next{pos},
+          element{std::forward<Args>(args)...} {}
+
+    std::size_t next;
+    Type element;
+};
+
+template<typename It>
+class dense_hash_set_iterator {
+    friend dense_hash_set_iterator<const std::remove_pointer_t<It> *>;
+
+    using iterator_traits = std::iterator_traits<decltype(std::addressof(std::as_const(std::declval<It>()->element)))>;
+
+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;
+
+    dense_hash_set_iterator() ENTT_NOEXCEPT = default;
+
+    dense_hash_set_iterator(const It iter) ENTT_NOEXCEPT
+        : it{iter} {}
+
+    template<bool Const = std::is_const_v<std::remove_pointer_t<It>>, typename = std::enable_if_t<Const>>
+    dense_hash_set_iterator(const dense_hash_set_iterator<std::remove_const_t<std::remove_pointer_t<It>> *> &other)
+        : it{other.it} {}
+
+    dense_hash_set_iterator &operator++() ENTT_NOEXCEPT {
+        return ++it, *this;
+    }
+
+    dense_hash_set_iterator operator++(int) ENTT_NOEXCEPT {
+        dense_hash_set_iterator orig = *this;
+        return ++(*this), orig;
+    }
+
+    dense_hash_set_iterator &operator--() ENTT_NOEXCEPT {
+        return --it, *this;
+    }
+
+    dense_hash_set_iterator operator--(int) ENTT_NOEXCEPT {
+        dense_hash_set_iterator orig = *this;
+        return operator--(), orig;
+    }
+
+    dense_hash_set_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT {
+        it += value;
+        return *this;
+    }
+
+    dense_hash_set_iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
+        dense_hash_set_iterator copy = *this;
+        return (copy += value);
+    }
+
+    dense_hash_set_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT {
+        return (*this += -value);
+    }
+
+    dense_hash_set_iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
+        return (*this + -value);
+    }
+
+    [[nodiscard]] reference operator[](const difference_type value) const {
+        return it[value].element;
+    }
+
+    [[nodiscard]] pointer operator->() const {
+        return std::addressof(it->element);
+    }
+
+    [[nodiscard]] reference operator*() const {
+        return *operator->();
+    }
+
+    template<typename ILhs, typename IRhs>
+    friend auto operator-(const dense_hash_set_iterator<ILhs> &, const dense_hash_set_iterator<IRhs> &) ENTT_NOEXCEPT;
+
+    template<typename ILhs, typename IRhs>
+    friend bool operator==(const dense_hash_set_iterator<ILhs> &, const dense_hash_set_iterator<IRhs> &) ENTT_NOEXCEPT;
+
+    template<typename ILhs, typename IRhs>
+    friend bool operator<(const dense_hash_set_iterator<ILhs> &, const dense_hash_set_iterator<IRhs> &) ENTT_NOEXCEPT;
+
+private:
+    It it;
+};
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] auto operator-(const dense_hash_set_iterator<ILhs> &lhs, const dense_hash_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return lhs.it - rhs.it;
+}
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator==(const dense_hash_set_iterator<ILhs> &lhs, const dense_hash_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return lhs.it == rhs.it;
+}
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator!=(const dense_hash_set_iterator<ILhs> &lhs, const dense_hash_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return !(lhs == rhs);
+}
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator<(const dense_hash_set_iterator<ILhs> &lhs, const dense_hash_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return lhs.it < rhs.it;
+}
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator>(const dense_hash_set_iterator<ILhs> &lhs, const dense_hash_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return rhs < lhs;
+}
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator<=(const dense_hash_set_iterator<ILhs> &lhs, const dense_hash_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return !(lhs > rhs);
+}
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator>=(const dense_hash_set_iterator<ILhs> &lhs, const dense_hash_set_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return !(lhs < rhs);
+}
+
+template<typename It>
+class dense_hash_set_local_iterator {
+    friend dense_hash_set_local_iterator<const std::remove_pointer_t<It> *>;
+
+    using iterator_traits = std::iterator_traits<decltype(std::addressof(std::as_const(std::declval<It>()->element)))>;
+
+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::forward_iterator_tag;
+
+    dense_hash_set_local_iterator() ENTT_NOEXCEPT = default;
+
+    dense_hash_set_local_iterator(It iter, const std::size_t pos) ENTT_NOEXCEPT
+        : it{iter},
+          offset{pos} {}
+
+    template<bool Const = std::is_const_v<std::remove_pointer_t<It>>, typename = std::enable_if_t<Const>>
+    dense_hash_set_local_iterator(const dense_hash_set_local_iterator<std::remove_const_t<std::remove_pointer_t<It>> *> &other)
+        : it{other.it},
+          offset{other.offset} {}
+
+    dense_hash_set_local_iterator &operator++() ENTT_NOEXCEPT {
+        return offset = it[offset].next, *this;
+    }
+
+    dense_hash_set_local_iterator operator++(int) ENTT_NOEXCEPT {
+        dense_hash_set_local_iterator orig = *this;
+        return ++(*this), orig;
+    }
+
+    [[nodiscard]] pointer operator->() const {
+        return std::addressof(it[offset].element);
+    }
+
+    [[nodiscard]] reference operator*() const {
+        return *operator->();
+    }
+
+    [[nodiscard]] std::size_t index() const ENTT_NOEXCEPT {
+        return offset;
+    }
+
+private:
+    It it;
+    std::size_t offset;
+};
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator==(const dense_hash_set_local_iterator<ILhs> &lhs, const dense_hash_set_local_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return lhs.index() == rhs.index();
+}
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator!=(const dense_hash_set_local_iterator<ILhs> &lhs, const dense_hash_set_local_iterator<IRhs> &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<typename Type, typename Hash, typename KeyEqual, typename Allocator>
+class dense_hash_set final {
+    static constexpr float default_threshold = 0.875f;
+    static constexpr std::size_t minimum_capacity = 8u;
+
+    using allocator_traits = std::allocator_traits<Allocator>;
+    using alloc = typename allocator_traits::template rebind_alloc<Type>;
+    using alloc_traits = typename std::allocator_traits<alloc>;
+
+    using node_type = internal::dense_hash_set_node<Type>;
+    using sparse_container_type = std::vector<std::size_t, typename alloc_traits::template rebind_alloc<std::size_t>>;
+    using packed_container_type = std::vector<node_type, typename alloc_traits::template rebind_alloc<node_type>>;
+
+    [[nodiscard]] std::size_t hash_to_bucket(const std::size_t hash) const ENTT_NOEXCEPT {
+        return fast_mod(hash, bucket_count());
+    }
+
+    template<typename Other>
+    [[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() + it.index();
+            }
+        }
+
+        return end();
+    }
+
+    template<typename Other>
+    [[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() + it.index();
+            }
+        }
+
+        return cend();
+    }
+
+    template<typename Arg>
+    [[nodiscard]] auto get_or_emplace(Arg &&arg) {
+        const auto hash = sparse.second()(arg);
+        auto index = hash_to_bucket(hash);
+
+        if(auto it = constrained_find(arg, index); it != end()) {
+            return std::make_pair(it, false);
+        }
+
+        if(const auto count = size() + 1u; count > (bucket_count() * max_load_factor())) {
+            rehash(bucket_count() * 2u);
+            index = hash_to_bucket(hash);
+        }
+
+        packed.first().emplace_back(sparse.first()[index], std::forward<Arg>(arg));
+        // update goes after emplace to enforce exception guarantees
+        sparse.first()[index] = size() - 1u;
+
+        return std::make_pair(--end(), true);
+    }
+
+    template<typename Other>
+    bool do_erase(const Other &value) {
+        for(size_type *curr = sparse.first().data() + bucket(value); *curr != std::numeric_limits<size_type>::max(); curr = &packed.first()[*curr].next) {
+            if(packed.second()(packed.first()[*curr].element, value)) {
+                const auto index = *curr;
+                *curr = packed.first()[*curr].next;
+                move_and_pop(index);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    void move_and_pop(const std::size_t pos) {
+        if(const auto last = size() - 1u; pos != last) {
+            size_type *curr = sparse.first().data() + bucket(packed.first().back().element);
+            for(; *curr != last; curr = &packed.first()[*curr].next) {}
+            *curr = pos;
+
+            // basic exception guarantees when value type has a throwing move constructor
+            packed.first()[pos] = std::move(packed.first().back());
+        }
+
+        packed.first().pop_back();
+    }
+
+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_hash_set_iterator<typename packed_container_type::pointer>;
+    /*! @brief Constant random access iterator type. */
+    using const_iterator = internal::dense_hash_set_iterator<typename packed_container_type::const_pointer>;
+    /*! @brief Forward iterator type. */
+    using local_iterator = internal::dense_hash_set_local_iterator<typename packed_container_type::pointer>;
+    /*! @brief Constant forward iterator type. */
+    using const_local_iterator = internal::dense_hash_set_local_iterator<typename packed_container_type::const_pointer>;
+
+    /*! @brief Default constructor. */
+    dense_hash_set()
+        : dense_hash_set(minimum_capacity) {}
+
+    /**
+     * @brief Constructs an empty container with a given allocator.
+     * @param allocator The allocator to use.
+     */
+    explicit dense_hash_set(const allocator_type &allocator)
+        : dense_hash_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_hash_set(const size_type bucket_count, const allocator_type &allocator)
+        : dense_hash_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_hash_set(const size_type bucket_count, const hasher &hash, const allocator_type &allocator)
+        : dense_hash_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_hash_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 Copy constructor.
+     * @param other The instance to copy from.
+     */
+    dense_hash_set(const dense_hash_set &other)
+        : dense_hash_set{other, alloc_traits::select_on_container_copy_construction(other.get_allocator())} {}
+
+    /**
+     * @brief Allocator-extended copy constructor.
+     * @param other The instance to copy from.
+     * @param allocator The allocator to use.
+     */
+    dense_hash_set(const dense_hash_set &other, const allocator_type &allocator)
+        : sparse{sparse_container_type{other.sparse.first(), allocator}, other.sparse.second()},
+          // cannot copy the container directly due to a nasty issue of apple clang :(
+          packed{packed_container_type{other.packed.first().begin(), other.packed.first().end(), allocator}, other.packed.second()},
+          threshold{other.threshold} {
+    }
+
+    /**
+     * @brief Default move constructor.
+     * @param other The instance to move from.
+     */
+    dense_hash_set(dense_hash_set &&other) ENTT_NOEXCEPT = default;
+
+    /**
+     * @brief Allocator-extended move constructor.
+     * @param other The instance to move from.
+     * @param allocator The allocator to use.
+     */
+    dense_hash_set(dense_hash_set &&other, const allocator_type &allocator) ENTT_NOEXCEPT
+        : sparse{sparse_container_type{std::move(other.sparse.first()), allocator}, std::move(other.sparse.second())},
+          // cannot move the container directly due to a nasty issue of apple clang :(
+          packed{packed_container_type{std::make_move_iterator(other.packed.first().begin()), std::make_move_iterator(other.packed.first().end()), allocator}, std::move(other.packed.second())},
+          threshold{other.threshold} {}
+
+    /*! @brief Default destructor. */
+    ~dense_hash_set() = default;
+
+    /**
+     * @brief Copy assignment operator.
+     * @param other The instance to copy from.
+     * @return This container.
+     */
+    dense_hash_set &operator=(const dense_hash_set &other) {
+        threshold = other.threshold;
+        sparse.first().clear();
+        packed.first().clear();
+        rehash(other.bucket_count());
+        packed.first().reserve(other.packed.first().size());
+        insert(other.cbegin(), other.cend());
+        return *this;
+    }
+
+    /**
+     * @brief Default move assignment operator.
+     * @param other The instance to move from.
+     * @return This container.
+     */
+    dense_hash_set &operator=(dense_hash_set &&other) ENTT_NOEXCEPT = 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().data();
+    }
+
+    /*! @copydoc cbegin */
+    [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT {
+        return cbegin();
+    }
+
+    /*! @copydoc begin */
+    [[nodiscard]] iterator begin() ENTT_NOEXCEPT {
+        return packed.first().data();
+    }
+
+    /**
+     * @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().data() + size();
+    }
+
+    /*! @copydoc cend */
+    [[nodiscard]] const_iterator end() const ENTT_NOEXCEPT {
+        return cend();
+    }
+
+    /*! @copydoc end */
+    [[nodiscard]] iterator end() ENTT_NOEXCEPT {
+        return packed.first().data() + size();
+    }
+
+    /**
+     * @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<iterator, bool> insert(const value_type &value) {
+        return emplace(value);
+    }
+
+    /*! @copydoc insert */
+    std::pair<iterator, bool> insert(value_type &&value) {
+        return emplace(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<typename It>
+    void insert(It first, It last) {
+        for(; first != last; ++first) {
+            emplace(*first);
+        }
+    }
+
+    /**
+     * @brief Constructs an element in-place, if it does not exist.
+     * @tparam Args Types of arguments to forward to the constructor of the
+     * element.
+     * @param args Arguments to forward to the constructor of the element.
+     * @return A pair consisting of an iterator to the inserted element (or to
+     * the element that prevented the insertion) and a bool denoting whether the
+     * insertion took place.
+     */
+    template<typename... Args>
+    std::pair<iterator, bool> emplace(Args &&...args) {
+        if constexpr(((sizeof...(Args) == 1u) && ... && std::is_same_v<std::remove_const_t<std::remove_reference_t<Args>>, value_type>)) {
+            return get_or_emplace(std::forward<Args>(args)...);
+        } else {
+            return get_or_emplace(value_type{std::forward<Args>(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 dist = std::distance(cbegin(), pos);
+        erase(*pos);
+        return begin() + dist;
+    }
+
+    /**
+     * @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 = std::distance(cbegin(), first);
+
+        for(auto rfirst = std::make_reverse_iterator(last), rlast = std::make_reverse_iterator(first); rfirst != rlast; ++rfirst) {
+            erase(*rfirst);
+        }
+
+        return dist > static_cast<decltype(dist)>(size()) ? end() : (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) {
+        return do_erase(value);
+    }
+
+    /**
+     * @brief Exchanges the contents with those of a given container.
+     * @param other Container to exchange the content with.
+     */
+    void swap(dense_hash_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, bucket(value));
+    }
+
+    /*! @copydoc find */
+    [[nodiscard]] const_iterator find(const value_type &value) const {
+        return constrained_find(value, 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<typename Other>
+    [[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, iterator>>
+    find(const Other &value) {
+        return constrained_find(value, bucket(value));
+    }
+
+    /*! @copydoc find */
+    template<typename Other>
+    [[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, const_iterator>>
+    find(const Other &value) const {
+        return constrained_find(value, 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<typename Other>
+    [[nodiscard]] std::enable_if_t<is_transparent_v<hasher> && is_transparent_v<key_equal>, std::conditional_t<false, Other, bool>>
+    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().data(), sparse.first()[index]};
+    }
+
+    /**
+     * @brief Returns an iterator to the beginning of a given bucket.
+     * @param index An index of a bucket to access.
+     * @return An iterator to the beginning of the given bucket.
+     */
+    [[nodiscard]] const_local_iterator begin(const size_type index) const {
+        return cbegin(index);
+    }
+
+    /**
+     * @brief Returns an iterator to the beginning of a given bucket.
+     * @param index An index of a bucket to access.
+     * @return An iterator to the beginning of the given bucket.
+     */
+    [[nodiscard]] local_iterator begin(const size_type index) {
+        return {packed.first().data(), sparse.first()[index]};
+    }
+
+    /**
+     * @brief Returns an iterator to the end of a given bucket.
+     * @param index An index of a bucket to access.
+     * @return An iterator to the end of the given bucket.
+     */
+    [[nodiscard]] const_local_iterator cend([[maybe_unused]] const size_type index) const {
+        return {packed.first().data(), std::numeric_limits<size_type>::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().data(), std::numeric_limits<size_type>::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<size_type>(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 hash_to_bucket(sparse.second()(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<float>(bucket_count());
+    }
+
+    /**
+     * @brief Returns the maximum average number of elements per bucket.
+     * @return The maximum average number of elements per bucket.
+     */
+    [[nodiscard]] float max_load_factor() const {
+        return threshold;
+    }
+
+    /**
+     * @brief Sets the desired maximum average number of elements per bucket.
+     * @param value A desired maximum average number of elements per bucket.
+     */
+    void max_load_factor(const float value) {
+        ENTT_ASSERT(value > 0.f, "Invalid load factor");
+        threshold = value;
+        rehash(0u);
+    }
+
+    /**
+     * @brief Reserves at least the specified number of buckets and regenerates
+     * the hash table.
+     * @param 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_type>(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<size_type>::max());
+
+            for(size_type pos{}, last = size(); pos < last; ++pos) {
+                const auto index = bucket(packed.first()[pos].element);
+                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<size_type>(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_container_type, hasher> sparse;
+    compressed_pair<packed_container_type, key_equal> packed;
+    float threshold;
+};
+
+} // namespace entt
+
+#endif

+ 25 - 0
Dependencies/include/entt/container/fwd.hpp

@@ -0,0 +1,25 @@
+#ifndef ENTT_CONTAINER_FWD_HPP
+#define ENTT_CONTAINER_FWD_HPP
+
+#include <functional>
+#include <memory>
+
+namespace entt {
+
+template<
+    typename Key, typename Type,
+    typename = std::hash<Key>,
+    typename = std::equal_to<Key>,
+    typename = std::allocator<std::pair<const Key, Type>>>
+class dense_hash_map;
+
+template<
+    typename Type,
+    typename = std::hash<Type>,
+    typename = std::equal_to<Type>,
+    typename = std::allocator<Type>>
+class dense_hash_set;
+
+} // namespace entt
+
+#endif

+ 137 - 0
Dependencies/include/entt/core/algorithm.hpp

@@ -0,0 +1,137 @@
+#ifndef ENTT_CORE_ALGORITHM_HPP
+#define ENTT_CORE_ALGORITHM_HPP
+
+#include <algorithm>
+#include <functional>
+#include <iterator>
+#include <utility>
+#include <vector>
+#include "utility.hpp"
+
+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.<br/>
+ * 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 It, typename Compare = std::less<>, typename... Args>
+    void operator()(It first, It last, Compare compare = Compare{}, Args &&...args) const {
+        std::sort(std::forward<Args>(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<typename It, typename Compare = std::less<>>
+    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<std::size_t Bit, std::size_t N>
+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<typename It, typename Getter = identity>
+    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<It>::value_type;
+            std::vector<value_type> 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

+ 494 - 0
Dependencies/include/entt/core/any.hpp

@@ -0,0 +1,494 @@
+#ifndef ENTT_CORE_ANY_HPP
+#define ENTT_CORE_ANY_HPP
+
+#include <cstddef>
+#include <memory>
+#include <type_traits>
+#include <utility>
+#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<std::size_t Len, std::size_t Align>
+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<Len + !Len, Align>;
+    using vtable_type = const void *(const operation, const basic_any &, const void *);
+
+    template<typename Type>
+    static constexpr bool in_situ = Len && alignof(Type) <= alignof(storage_type) && sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v<Type>;
+
+    template<typename Type>
+    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<Type, void> && std::is_same_v<std::remove_reference_t<std::remove_const_t<Type>>, Type>, "Invalid type");
+        const Type *instance = nullptr;
+
+        if constexpr(in_situ<Type>) {
+            instance = (value.mode == policy::owner) ? ENTT_LAUNDER(reinterpret_cast<const Type *>(&value.storage)) : static_cast<const Type *>(value.instance);
+        } else {
+            instance = static_cast<const Type *>(value.instance);
+        }
+
+        switch(op) {
+        case operation::copy:
+            if constexpr(std::is_copy_constructible_v<Type>) {
+                static_cast<basic_any *>(const_cast<void *>(other))->initialize<Type>(*instance);
+            }
+            break;
+        case operation::move:
+            if constexpr(in_situ<Type>) {
+                if(value.mode == policy::owner) {
+                    return new(&static_cast<basic_any *>(const_cast<void *>(other))->storage) Type{std::move(*const_cast<Type *>(instance))};
+                }
+            }
+
+            return (static_cast<basic_any *>(const_cast<void *>(other))->instance = std::exchange(const_cast<basic_any &>(value).instance, nullptr));
+        case operation::transfer:
+            if constexpr(std::is_move_assignable_v<Type>) {
+                *const_cast<Type *>(instance) = std::move(*static_cast<Type *>(const_cast<void *>(other)));
+                return other;
+            }
+            [[fallthrough]];
+        case operation::assign:
+            if constexpr(std::is_copy_assignable_v<Type>) {
+                *const_cast<Type *>(instance) = *static_cast<const Type *>(other);
+                return other;
+            }
+            break;
+        case operation::destroy:
+            if constexpr(in_situ<Type>) {
+                instance->~Type();
+            } else if constexpr(std::is_array_v<Type>) {
+                delete[] instance;
+            } else {
+                delete instance;
+            }
+            break;
+        case operation::compare:
+            if constexpr(!std::is_function_v<Type> && !std::is_array_v<Type> && is_equality_comparable_v<Type>) {
+                return *static_cast<const Type *>(instance) == *static_cast<const Type *>(other) ? other : nullptr;
+            } else {
+                return (instance == other) ? other : nullptr;
+            }
+        case operation::get:
+            return instance;
+        }
+
+        return nullptr;
+    }
+
+    template<typename Type, typename... Args>
+    void initialize([[maybe_unused]] Args &&...args) {
+        if constexpr(!std::is_void_v<Type>) {
+            vtable = basic_vtable<std::remove_const_t<std::remove_reference_t<Type>>>;
+            info = &type_id<Type>();
+
+            if constexpr(std::is_lvalue_reference_v<Type>) {
+                static_assert(sizeof...(Args) == 1u && (std::is_lvalue_reference_v<Args> && ...), "Invalid arguments");
+                mode = std::is_const_v<std::remove_reference_t<Type>> ? policy::cref : policy::ref;
+                instance = (std::addressof(args), ...);
+            } else if constexpr(in_situ<Type>) {
+                if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<Type>) {
+                    new(&storage) Type{std::forward<Args>(args)...};
+                } else {
+                    new(&storage) Type(std::forward<Args>(args)...);
+                }
+            } else {
+                if constexpr(sizeof...(Args) != 0u && std::is_aggregate_v<Type>) {
+                    instance = new Type{std::forward<Args>(args)...};
+                } else {
+                    instance = new Type(std::forward<Args>(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. */
+    basic_any() ENTT_NOEXCEPT
+        : instance{},
+          info{&type_id<void>()},
+          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<typename Type, typename... Args>
+    explicit basic_any(std::in_place_type_t<Type>, Args &&...args)
+        : basic_any{} {
+        initialize<Type>(std::forward<Args>(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<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Type>, basic_any>>>
+    basic_any(Type &&value)
+        : basic_any{} {
+        initialize<std::decay_t<Type>>(std::forward<Type>(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 && mode == policy::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<typename Type>
+    std::enable_if_t<!std::is_same_v<std::decay_t<Type>, basic_any>, basic_any &>
+    operator=(Type &&value) {
+        emplace<std::decay_t<Type>>(std::forward<Type>(value));
+        return *this;
+    }
+
+    /**
+     * @brief Returns the object type if any, `type_id<void>()` otherwise.
+     * @return The object type if any, `type_id<void>()` 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<void *>(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<typename Type, typename... Args>
+    void emplace(Args &&...args) {
+        reset();
+        initialize<Type>(std::forward<Args>(args)...);
+    }
+
+    /**
+     * @brief Copy 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;
+    }
+
+    /**
+     * @brief Move 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(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 && mode == policy::owner) {
+            vtable(operation::destroy, *this, nullptr);
+        }
+
+        info = &type_id<void>();
+        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<std::size_t Len, std::size_t Align>
+[[nodiscard]] inline bool operator!=(const basic_any<Len, Align> &lhs, const basic_any<Len, Align> &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<typename Type, std::size_t Len, std::size_t Align>
+Type any_cast(const basic_any<Len, Align> &data) ENTT_NOEXCEPT {
+    const auto *const instance = any_cast<std::remove_reference_t<Type>>(&data);
+    ENTT_ASSERT(instance, "Invalid instance");
+    return static_cast<Type>(*instance);
+}
+
+/*! @copydoc any_cast */
+template<typename Type, std::size_t Len, std::size_t Align>
+Type any_cast(basic_any<Len, Align> &data) ENTT_NOEXCEPT {
+    // forces const on non-reference types to make them work also with wrappers for const references
+    auto *const instance = any_cast<std::remove_reference_t<const Type>>(&data);
+    ENTT_ASSERT(instance, "Invalid instance");
+    return static_cast<Type>(*instance);
+}
+
+/*! @copydoc any_cast */
+template<typename Type, std::size_t Len, std::size_t Align>
+Type any_cast(basic_any<Len, Align> &&data) ENTT_NOEXCEPT {
+    if constexpr(std::is_copy_constructible_v<std::remove_const_t<std::remove_reference_t<Type>>>) {
+        if(auto *const instance = any_cast<std::remove_reference_t<Type>>(&data); instance) {
+            return static_cast<Type>(std::move(*instance));
+        } else {
+            return any_cast<Type>(data);
+        }
+    } else {
+        auto *const instance = any_cast<std::remove_reference_t<Type>>(&data);
+        ENTT_ASSERT(instance, "Invalid instance");
+        return static_cast<Type>(std::move(*instance));
+    }
+}
+
+/*! @copydoc any_cast */
+template<typename Type, std::size_t Len, std::size_t Align>
+const Type *any_cast(const basic_any<Len, Align> *data) ENTT_NOEXCEPT {
+    const auto &info = type_id<std::remove_const_t<std::remove_reference_t<Type>>>();
+    return static_cast<const Type *>(data->data(info));
+}
+
+/*! @copydoc any_cast */
+template<typename Type, std::size_t Len, std::size_t Align>
+Type *any_cast(basic_any<Len, Align> *data) ENTT_NOEXCEPT {
+    const auto &info = type_id<std::remove_const_t<std::remove_reference_t<Type>>>();
+    // last attempt to make wrappers for const references return their values
+    return static_cast<Type *>(static_cast<constness_as_t<basic_any<Len, Align>, 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<typename Type, std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename... Args>
+basic_any<Len, Align> make_any(Args &&...args) {
+    return basic_any<Len, Align>{std::in_place_type<Type>, std::forward<Args>(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<std::size_t Len = basic_any<>::length, std::size_t Align = basic_any<Len>::alignment, typename Type>
+basic_any<Len, Align> forward_as_any(Type &&value) {
+    return basic_any<Len, Align>{std::in_place_type<std::conditional_t<std::is_rvalue_reference_v<Type>, std::decay_t<Type>, Type>>, std::forward<Type>(value)};
+}
+
+} // namespace entt
+
+#endif

+ 30 - 0
Dependencies/include/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

+ 269 - 0
Dependencies/include/entt/core/compressed_pair.hpp

@@ -0,0 +1,269 @@
+#ifndef ENTT_CORE_COMPRESSED_PAIR_HPP
+#define ENTT_CORE_COMPRESSED_PAIR_HPP
+
+#include <cstddef>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include "../config/config.h"
+#include "type_traits.hpp"
+
+namespace entt {
+
+/**
+ * @cond TURN_OFF_DOXYGEN
+ * Internal details not to be documented.
+ */
+
+namespace internal {
+
+template<typename Type, std::size_t, typename = void>
+struct compressed_pair_element {
+    using reference = Type &;
+    using const_reference = const Type &;
+
+    template<bool Dummy = true, typename = std::enable_if_t<Dummy && std::is_default_constructible_v<Type>>>
+    compressed_pair_element()
+        : value{} {}
+
+    template<typename Args, typename = std::enable_if_t<!std::is_same_v<std::remove_const_t<std::remove_reference_t<Args>>, compressed_pair_element>>>
+    compressed_pair_element(Args &&args)
+        : value{std::forward<Args>(args)} {}
+
+    template<typename... Args, std::size_t... Index>
+    compressed_pair_element(std::tuple<Args...> args, std::index_sequence<Index...>)
+        : value{std::get<Index>(args)...} {}
+
+    [[nodiscard]] reference get() ENTT_NOEXCEPT {
+        return value;
+    }
+
+    [[nodiscard]] const_reference get() const ENTT_NOEXCEPT {
+        return value;
+    }
+
+private:
+    Type value;
+};
+
+template<typename Type, std::size_t Tag>
+struct compressed_pair_element<Type, Tag, std::enable_if_t<is_ebco_eligible_v<Type>>>: Type {
+    using reference = Type &;
+    using const_reference = const Type &;
+    using base_type = Type;
+
+    template<bool Dummy = true, typename = std::enable_if_t<Dummy && std::is_default_constructible_v<base_type>>>
+    compressed_pair_element()
+        : base_type{} {}
+
+    template<typename Args, typename = std::enable_if_t<!std::is_same_v<std::remove_const_t<std::remove_reference_t<Args>>, compressed_pair_element>>>
+    compressed_pair_element(Args &&args)
+        : base_type{std::forward<Args>(args)} {}
+
+    template<typename... Args, std::size_t... Index>
+    compressed_pair_element(std::tuple<Args...> args, std::index_sequence<Index...>)
+        : base_type{std::get<Index>(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<typename First, typename Second>
+class compressed_pair final
+    : internal::compressed_pair_element<First, 0u>,
+      internal::compressed_pair_element<Second, 1u> {
+    using first_base = internal::compressed_pair_element<First, 0u>;
+    using second_base = internal::compressed_pair_element<Second, 1u>;
+
+public:
+    /*! @brief The type of the first element that the pair stores. */
+    using first_type = First;
+    /*! @brief The type of the second element that the pair stores. */
+    using second_type = Second;
+
+    /**
+     * @brief Default constructor, conditionally enabled.
+     *
+     * This constructor is only available when the types that the pair stores
+     * are both at least default constructible.
+     *
+     * @tparam Dummy Dummy template parameter used for internal purposes.
+     */
+    template<bool Dummy = true, typename = std::enable_if_t<Dummy && std::is_default_constructible_v<first_type> && std::is_default_constructible_v<second_type>>>
+    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) ENTT_NOEXCEPT = default;
+
+    /**
+     * @brief Constructs a pair from its values.
+     * @tparam Arg Type of value to use to initialize the first element.
+     * @tparam Other Type of value to use to initialize the second element.
+     * @param arg Value to use to initialize the first element.
+     * @param other Value to use to initialize the second element.
+     */
+    template<typename Arg, typename Other>
+    constexpr compressed_pair(Arg &&arg, Other &&other)
+        : first_base{std::forward<Arg>(arg)},
+          second_base{std::forward<Other>(other)} {}
+
+    /**
+     * @brief Constructs a pair by forwarding the arguments to its parts.
+     * @tparam Args Types of arguments to use to initialize the first element.
+     * @tparam Other Types of arguments to use to initialize the second element.
+     * @param args Arguments to use to initialize the first element.
+     * @param other Arguments to use to initialize the second element.
+     */
+    template<typename... Args, typename... Other>
+    constexpr compressed_pair(std::piecewise_construct_t, std::tuple<Args...> args, std::tuple<Other...> other)
+        : first_base{std::move(args), std::index_sequence_for<Args...>{}},
+          second_base{std::move(other), std::index_sequence_for<Other...>{}} {}
+
+    /**
+     * @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) ENTT_NOEXCEPT = 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<first_base &>(*this).get();
+    }
+
+    /*! @copydoc first */
+    [[nodiscard]] const first_type &first() const ENTT_NOEXCEPT {
+        return static_cast<const first_base &>(*this).get();
+    }
+
+    /**
+     * @brief Returns the second element that a pair stores.
+     * @return The second element that a pair stores.
+     */
+    [[nodiscard]] second_type &second() ENTT_NOEXCEPT {
+        return static_cast<second_base &>(*this).get();
+    }
+
+    /*! @copydoc second */
+    [[nodiscard]] const second_type &second() const ENTT_NOEXCEPT {
+        return static_cast<const second_base &>(*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<std::size_t Index>
+    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<std::size_t Index>
+    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<typename Type, typename Other>
+compressed_pair(Type &&, Other &&) -> compressed_pair<std::decay_t<Type>, std::decay_t<Other>>;
+
+/**
+ * @brief Swaps two compressed pair objects.
+ * @tparam First The type of the first element that the pairs store.
+ * @tparam Second The type of the second element that the pairs store.
+ * @param lhs A valid compressed pair object.
+ * @param rhs A valid compressed pair object.
+ */
+template<typename First, typename Second>
+inline void swap(compressed_pair<First, Second> &lhs, compressed_pair<First, Second> &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 {
+
+template<typename First, typename Second>
+struct tuple_size<entt::compressed_pair<First, Second>>: integral_constant<size_t, 2u> {};
+
+template<size_t Index, typename First, typename Second>
+struct tuple_element<Index, entt::compressed_pair<First, Second>>: conditional<Index == 0u, First, Second> {
+    static_assert(Index < 2u, "Index out of bounds");
+};
+
+} // namespace std
+#endif
+
+#endif

+ 112 - 0
Dependencies/include/entt/core/enum.hpp

@@ -0,0 +1,112 @@
+#ifndef ENTT_CORE_FLAG_HPP
+#define ENTT_CORE_FLAG_HPP
+
+#include <type_traits>
+#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<typename Type, typename = void>
+struct enum_as_bitmask: std::false_type {};
+
+/*! @copydoc enum_as_bitmask */
+template<typename Type>
+struct enum_as_bitmask<Type, std::void_t<decltype(Type::_entt_enum_as_bitmask)>>: std::true_type {};
+
+/**
+ * @brief Helper variable template.
+ * @tparam Type The enum class type for which to enable bitmask support.
+ */
+template<typename Type>
+inline constexpr bool enum_as_bitmask_v = enum_as_bitmask<Type>::value;
+
+} // 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<typename Type>
+[[nodiscard]] constexpr std::enable_if_t<std::is_enum_v<Type> && entt::enum_as_bitmask_v<Type>, Type>
+operator|(const Type lhs, const Type rhs) ENTT_NOEXCEPT {
+    return Type{static_cast<std::underlying_type_t<Type>>(lhs) | static_cast<std::underlying_type_t<Type>>(rhs)};
+}
+
+/*! @copydoc operator| */
+template<typename Type>
+[[nodiscard]] constexpr std::enable_if_t<std::is_enum_v<Type> && entt::enum_as_bitmask_v<Type>, Type>
+operator&(const Type lhs, const Type rhs) ENTT_NOEXCEPT {
+    return Type{static_cast<std::underlying_type_t<Type>>(lhs) & static_cast<std::underlying_type_t<Type>>(rhs)};
+}
+
+/*! @copydoc operator| */
+template<typename Type>
+[[nodiscard]] constexpr std::enable_if_t<std::is_enum_v<Type> && entt::enum_as_bitmask_v<Type>, Type>
+operator^(const Type lhs, const Type rhs) ENTT_NOEXCEPT {
+    return Type{static_cast<std::underlying_type_t<Type>>(lhs) ^ static_cast<std::underlying_type_t<Type>>(rhs)};
+}
+
+/**
+ * @brief Operator available for enums for which bitmask support is enabled.
+ * @tparam Type Enum class type.
+ * @param value The value to use.
+ * @return The result of invoking the operator on the underlying types of the
+ * value provided.
+ */
+template<typename Type>
+[[nodiscard]] constexpr std::enable_if_t<std::is_enum_v<Type> && entt::enum_as_bitmask_v<Type>, Type>
+operator~(const Type value) ENTT_NOEXCEPT {
+    return Type{~static_cast<std::underlying_type_t<Type>>(value)};
+}
+
+/*! @copydoc operator~ */
+template<typename Type>
+[[nodiscard]] constexpr std::enable_if_t<std::is_enum_v<Type> && entt::enum_as_bitmask_v<Type>, bool>
+operator!(const Type value) ENTT_NOEXCEPT {
+    return !static_cast<std::underlying_type_t<Type>>(value);
+}
+
+/*! @copydoc operator| */
+template<typename Type>
+constexpr std::enable_if_t<std::is_enum_v<Type> && entt::enum_as_bitmask_v<Type>, Type &>
+operator|=(Type &lhs, const Type rhs) ENTT_NOEXCEPT {
+    return (lhs = (lhs | rhs));
+}
+
+/*! @copydoc operator| */
+template<typename Type>
+constexpr std::enable_if_t<std::is_enum_v<Type> && entt::enum_as_bitmask_v<Type>, Type &>
+operator&=(Type &lhs, const Type rhs) ENTT_NOEXCEPT {
+    return (lhs = (lhs & rhs));
+}
+
+/*! @copydoc operator| */
+template<typename Type>
+constexpr std::enable_if_t<std::is_enum_v<Type> && entt::enum_as_bitmask_v<Type>, Type &>
+operator^=(Type &lhs, const Type rhs) ENTT_NOEXCEPT {
+    return (lhs = (lhs ^ rhs));
+}
+
+/*! @copydoc operator| */
+template<typename Type>
+[[nodiscard]] constexpr std::enable_if_t<std::is_enum_v<Type> && entt::enum_as_bitmask_v<Type>, Type>
+operator==(const Type lhs, const Type rhs) ENTT_NOEXCEPT {
+    return (static_cast<std::underlying_type_t<Type>>(lhs) == static_cast<std::underlying_type_t<Type>>(rhs));
+}
+
+/*! @copydoc operator| */
+template<typename Type>
+[[nodiscard]] constexpr std::enable_if_t<std::is_enum_v<Type> && entt::enum_as_bitmask_v<Type>, Type>
+operator!=(const Type lhs, const Type rhs) ENTT_NOEXCEPT {
+    return !(lhs == rhs);
+}
+
+#endif

+ 32 - 0
Dependencies/include/entt/core/family.hpp

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

+ 20 - 0
Dependencies/include/entt/core/fwd.hpp

@@ -0,0 +1,20 @@
+#ifndef ENTT_CORE_FWD_HPP
+#define ENTT_CORE_FWD_HPP
+
+#include <type_traits>
+#include "../config/config.h"
+
+namespace entt {
+
+template<std::size_t Len = sizeof(double[2]), std::size_t = alignof(typename std::aligned_storage_t<Len + !Len>)>
+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

+ 314 - 0
Dependencies/include/entt/core/hashed_string.hpp

@@ -0,0 +1,314 @@
+#ifndef ENTT_CORE_HASHED_STRING_HPP
+#define ENTT_CORE_HASHED_STRING_HPP
+
+#include <cstddef>
+#include <cstdint>
+#include "../config/config.h"
+#include "fwd.hpp"
+
+namespace entt {
+
+/**
+ * @cond TURN_OFF_DOXYGEN
+ * Internal details not to be documented.
+ */
+
+namespace internal {
+
+template<typename>
+struct fnv1a_traits;
+
+template<>
+struct fnv1a_traits<std::uint32_t> {
+    using type = std::uint32_t;
+    static constexpr std::uint32_t offset = 2166136261;
+    static constexpr std::uint32_t prime = 16777619;
+};
+
+template<>
+struct fnv1a_traits<std::uint64_t> {
+    using type = std::uint64_t;
+    static constexpr std::uint64_t offset = 14695981039346656037ull;
+    static constexpr std::uint64_t prime = 1099511628211ull;
+};
+
+} // 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 identifers in the codebase while using their numeric
+ * counterparts at runtime.<br/>
+ * Because of that, a hashed string can also be used in constant expressions if
+ * required.
+ *
+ * @warning
+ * This class doesn't take ownership of user-supplied strings nor does it make a
+ * copy of them.
+ *
+ * @tparam Char Character type.
+ */
+template<typename Char>
+class basic_hashed_string {
+    using hs_traits = internal::fnv1a_traits<id_type>;
+
+    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
+    [[nodiscard]] static constexpr id_type helper(const Char *curr) ENTT_NOEXCEPT {
+        auto value = hs_traits::offset;
+
+        while(*curr != 0) {
+            value = (value ^ static_cast<hs_traits::type>(*(curr++))) * hs_traits::prime;
+        }
+
+        return value;
+    }
+
+public:
+    /*! @brief Character type. */
+    using value_type = Char;
+    /*! @brief Unsigned integer type. */
+    using hash_type = id_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.
+     */
+    [[nodiscard]] static constexpr hash_type value(const value_type *str, std::size_t size) ENTT_NOEXCEPT {
+        id_type partial{hs_traits::offset};
+        while(size--) { partial = (partial ^ (str++)[0]) * hs_traits::prime; }
+        return partial;
+    }
+
+    /**
+     * @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.<br/>
+     * Example of use:
+     * @code{.cpp}
+     * const auto value = basic_hashed_string<char>::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<std::size_t N>
+    [[nodiscard]] static constexpr hash_type value(const value_type (&str)[N]) ENTT_NOEXCEPT {
+        return helper(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 hash_type value(const_wrapper wrapper) ENTT_NOEXCEPT {
+        return helper(wrapper.str);
+    }
+
+    /*! @brief Constructs an empty hashed string. */
+    constexpr basic_hashed_string() ENTT_NOEXCEPT
+        : str{nullptr},
+          hash{} {}
+
+    /**
+     * @brief Constructs a hashed string from an array of const characters.
+     *
+     * Forcing template resolution avoids implicit conversions. An
+     * human-readable identifier can be anything but a plain, old bunch of
+     * characters.<br/>
+     * Example of use:
+     * @code{.cpp}
+     * basic_hashed_string<char> hs{"my.png"};
+     * @endcode
+     *
+     * @tparam N Number of characters of the identifier.
+     * @param curr Human-readable identifer.
+     */
+    template<std::size_t N>
+    constexpr basic_hashed_string(const value_type (&curr)[N]) ENTT_NOEXCEPT
+        : str{curr},
+          hash{helper(curr)} {}
+
+    /**
+     * @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
+        : str{wrapper.str},
+          hash{helper(wrapper.str)} {}
+
+    /**
+     * @brief Returns the human-readable representation of a hashed string.
+     * @return The string used to initialize the instance.
+     */
+    [[nodiscard]] constexpr const value_type *data() const ENTT_NOEXCEPT {
+        return str;
+    }
+
+    /**
+     * @brief Returns the numeric representation of a hashed string.
+     * @return The numeric representation of the instance.
+     */
+    [[nodiscard]] constexpr hash_type value() const ENTT_NOEXCEPT {
+        return 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 instance.
+     */
+    [[nodiscard]] constexpr operator hash_type() const ENTT_NOEXCEPT {
+        return value();
+    }
+
+private:
+    const value_type *str;
+    hash_type hash;
+};
+
+/**
+ * @brief Deduction guide.
+ *
+ * It allows to deduce the character type of the hashed string directly from a
+ * human-readable identifer provided to the constructor.
+ *
+ * @tparam Char Character type.
+ * @tparam N Number of characters of the identifier.
+ * @param str Human-readable identifer.
+ */
+template<typename Char, std::size_t N>
+basic_hashed_string(const Char (&str)[N]) -> basic_hashed_string<Char>;
+
+/**
+ * @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<typename Char>
+[[nodiscard]] constexpr bool operator==(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &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<typename Char>
+[[nodiscard]] constexpr bool operator!=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &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<typename Char>
+[[nodiscard]] constexpr bool operator<(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &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<typename Char>
+[[nodiscard]] constexpr bool operator<=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &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<typename Char>
+[[nodiscard]] constexpr bool operator>(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &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<typename Char>
+[[nodiscard]] constexpr bool operator>=(const basic_hashed_string<Char> &lhs, const basic_hashed_string<Char> &rhs) ENTT_NOEXCEPT {
+    return !(lhs < rhs);
+}
+
+/*! @brief Aliases for common character types. */
+using hashed_string = basic_hashed_string<char>;
+
+/*! @brief Aliases for common character types. */
+using hashed_wstring = basic_hashed_string<wchar_t>;
+
+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 entt::hashed_string operator"" _hs(const char *str, std::size_t) ENTT_NOEXCEPT {
+    return entt::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 entt::hashed_wstring operator"" _hws(const wchar_t *str, std::size_t) ENTT_NOEXCEPT {
+    return entt::hashed_wstring{str};
+}
+
+} // namespace literals
+
+} // namespace entt
+
+#endif

+ 59 - 0
Dependencies/include/entt/core/ident.hpp

@@ -0,0 +1,59 @@
+#ifndef ENTT_CORE_IDENT_HPP
+#define ENTT_CORE_IDENT_HPP
+
+#include <cstddef>
+#include <type_traits>
+#include <utility>
+#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<a_type, another_type>;
+ *
+ * switch(a_type_identifier) {
+ * case id::type<a_type>:
+ *     // ...
+ *     break;
+ * case id::type<another_type>:
+ *     // ...
+ *     break;
+ * default:
+ *     // ...
+ * }
+ * @endcode
+ *
+ * @tparam Types List of types for which to generate identifiers.
+ */
+template<typename... Types>
+class identifier {
+    template<typename Type, std::size_t... Index>
+    [[nodiscard]] static constexpr id_type get(std::index_sequence<Index...>) {
+        static_assert(std::disjunction_v<std::is_same<Type, Types>...>, "Invalid type");
+        return (0 + ... + (std::is_same_v<Type, type_list_element_t<Index, type_list<std::decay_t<Types>...>>> ? id_type{Index} : id_type{}));
+    }
+
+public:
+    /*! @brief Unsigned integer type. */
+    using identifier_type = id_type;
+
+    /*! @brief Statically generated unique identifier for the given type. */
+    template<typename Type>
+    static constexpr identifier_type type = get<std::decay_t<Type>>(std::index_sequence_for<Types...>{});
+};
+
+} // namespace entt
+
+#endif

+ 116 - 0
Dependencies/include/entt/core/iterator.hpp

@@ -0,0 +1,116 @@
+#ifndef ENTT_CORE_ITERATOR_HPP
+#define ENTT_CORE_ITERATOR_HPP
+
+#include <iterator>
+#include <memory>
+#include <utility>
+#include "../config/config.h"
+
+namespace entt {
+
+/**
+ * @brief Helper type to use as pointer with input iterators.
+ * @tparam Type of wrapped value.
+ */
+template<typename Type>
+struct input_iterator_pointer final {
+    /*! @brief Pointer type. */
+    using pointer = decltype(std::addressof(std::declval<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 iterators.
+ */
+template<typename It>
+struct iterable_adaptor final {
+    /*! @brief Type of the objects returned during iteration. */
+    using value_type = typename std::iterator_traits<It>::value_type;
+    /*! @brief Iterator type. */
+    using iterator = It;
+    /*! @brief Const iterator type. */
+    using const_iterator = iterator;
+
+    /*! @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(It from, It to)
+        : first{from},
+          last{to} {}
+
+    /**
+     * @brief Returns an iterator to the beginning.
+     * @return An iterator to the first element of the range.
+     */
+    [[nodiscard]] const_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]] const_iterator end() const ENTT_NOEXCEPT {
+        return last;
+    }
+
+    /*! @copydoc begin */
+    [[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT {
+        return begin();
+    }
+
+    /*! @copydoc end */
+    [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT {
+        return end();
+    }
+
+private:
+    It first;
+    It last;
+};
+
+} // namespace entt
+
+#endif

+ 108 - 0
Dependencies/include/entt/core/memory.hpp

@@ -0,0 +1,108 @@
+#ifndef ENTT_CORE_MEMORY_HPP
+#define ENTT_CORE_MEMORY_HPP
+
+#include <cstddef>
+#include <limits>
+#include <memory>
+#include <type_traits>
+#include <utility>
+#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<typename Type>
+[[nodiscard]] constexpr auto to_address(Type &&ptr) ENTT_NOEXCEPT {
+    if constexpr(std::is_pointer_v<std::remove_const_t<std::remove_reference_t<Type>>>) {
+        return ptr;
+    } else {
+        return to_address(std::forward<Type>(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<typename Allocator>
+constexpr void propagate_on_container_copy_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) ENTT_NOEXCEPT {
+    if constexpr(std::allocator_traits<Allocator>::propagate_on_container_copy_assignment::value) {
+        lhs = rhs;
+    }
+}
+
+/**
+ * @brief Utility function to design allocation-aware containers.
+ * @tparam Allocator Type of allocator.
+ * @param lhs A valid allocator.
+ * @param rhs Another valid allocator.
+ */
+template<typename Allocator>
+constexpr void propagate_on_container_move_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) ENTT_NOEXCEPT {
+    if constexpr(std::allocator_traits<Allocator>::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<typename Allocator>
+constexpr void propagate_on_container_swap([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) ENTT_NOEXCEPT {
+    ENTT_ASSERT(std::allocator_traits<Allocator>::propagate_on_container_swap::value || lhs == rhs, "Cannot swap the containers");
+
+    if constexpr(std::allocator_traits<Allocator>::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<std::size_t>::digits - 1)), "Numeric limits exceeded");
+    std::size_t curr = value - (value != 0u);
+
+    for(int next = 1; next < std::numeric_limits<std::size_t>::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);
+}
+
+} // namespace entt
+
+#endif

+ 56 - 0
Dependencies/include/entt/core/monostate.hpp

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

+ 29 - 0
Dependencies/include/entt/core/tuple.hpp

@@ -0,0 +1,29 @@
+#ifndef ENTT_CORE_TUPLE_HPP
+#define ENTT_CORE_TUPLE_HPP
+
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#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<typename Type>
+constexpr decltype(auto) unwrap_tuple(Type &&value) ENTT_NOEXCEPT {
+    if constexpr(std::tuple_size_v<std::remove_reference_t<Type>> == 1u) {
+        return std::get<0>(std::forward<Type>(value));
+    } else {
+        return std::forward<Type>(value);
+    }
+}
+
+} // namespace entt
+
+#endif

+ 267 - 0
Dependencies/include/entt/core/type_info.hpp

@@ -0,0 +1,267 @@
+#ifndef ENTT_CORE_TYPE_INFO_HPP
+#define ENTT_CORE_TYPE_INFO_HPP
+
+#include <string_view>
+#include <type_traits>
+#include <utility>
+#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<typename Type>
+[[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<typename Type, auto = stripped_type_name<Type>().find_first_of('.')>
+[[nodiscard]] static constexpr std::string_view type_name(int) ENTT_NOEXCEPT {
+    constexpr auto value = stripped_type_name<Type>();
+    return value;
+}
+
+template<typename Type>
+[[nodiscard]] static std::string_view type_name(char) ENTT_NOEXCEPT {
+    static const auto value = stripped_type_name<Type>();
+    return value;
+}
+
+template<typename Type, auto = stripped_type_name<Type>().find_first_of('.')>
+[[nodiscard]] static constexpr id_type type_hash(int) ENTT_NOEXCEPT {
+    constexpr auto stripped = stripped_type_name<Type>();
+    constexpr auto value = hashed_string::value(stripped.data(), stripped.size());
+    return value;
+}
+
+template<typename Type>
+[[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<Type>());
+    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<typename Type, typename = void>
+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<typename Type, typename = void>
+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<Type>(0);
+#else
+    [[nodiscard]] static constexpr id_type value() ENTT_NOEXCEPT {
+        return type_index<Type>::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<typename Type, typename = void>
+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<Type>(0);
+    }
+
+    /*! @copydoc value */
+    [[nodiscard]] constexpr operator std::string_view() const ENTT_NOEXCEPT {
+        return value();
+    }
+};
+
+/*! @brief Implementation specific information about a type. */
+class type_info final {
+    template<typename Type>
+    friend const type_info &type_id() ENTT_NOEXCEPT;
+
+    template<typename Type>
+    constexpr type_info(std::in_place_type_t<Type>) ENTT_NOEXCEPT
+        : seq{type_index<std::remove_reference_t<std::remove_const_t<Type>>>::value()},
+          identifier{type_hash<std::remove_reference_t<std::remove_const_t<Type>>>::value()},
+          alias{type_name<std::remove_reference_t<std::remove_const_t<Type>>>::value()} {}
+
+public:
+    /**
+     * @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 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 properly initialized type info object.
+ */
+template<typename Type>
+[[nodiscard]] const type_info &type_id() ENTT_NOEXCEPT {
+    if constexpr(std::is_same_v<Type, std::remove_cv_t<std::remove_reference_t<Type>>>) {
+        static type_info instance{std::in_place_type<std::remove_cv_t<std::remove_reference_t<Type>>>};
+        return instance;
+    } else {
+        return type_id<std::remove_cv_t<std::remove_reference_t<Type>>>();
+    }
+}
+
+} // namespace entt
+
+#endif

+ 678 - 0
Dependencies/include/entt/core/type_traits.hpp

@@ -0,0 +1,678 @@
+#ifndef ENTT_CORE_TYPE_TRAITS_HPP
+#define ENTT_CORE_TYPE_TRAITS_HPP
+
+#include <cstddef>
+#include <iterator>
+#include <type_traits>
+#include <utility>
+#include "../config/config.h"
+#include "fwd.hpp"
+
+namespace entt {
+
+/**
+ * @brief Utility class to disambiguate overloaded functions.
+ * @tparam N Number of choices available.
+ */
+template<std::size_t N>
+struct choice_t
+    // Unfortunately, doxygen cannot parse such a construct.
+    : /*! @cond TURN_OFF_DOXYGEN */ choice_t<N - 1> /*! @endcond */
+{};
+
+/*! @copybrief choice_t */
+template<>
+struct choice_t<0> {};
+
+/**
+ * @brief Variable template for the choice trick.
+ * @tparam N Number of choices available.
+ */
+template<std::size_t N>
+inline constexpr choice_t<N> 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<typename Type>
+struct type_identity {
+    /*! @brief Identity type. */
+    using type = Type;
+};
+
+/**
+ * @brief Helper type.
+ * @tparam Type A type.
+ */
+template<typename Type>
+using type_identity_t = typename type_identity<Type>::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<typename Type, typename = void>
+struct size_of: std::integral_constant<std::size_t, 0u> {};
+
+/*! @copydoc size_of */
+template<typename Type>
+struct size_of<Type, std::void_t<decltype(sizeof(Type))>>
+    : std::integral_constant<std::size_t, sizeof(Type)> {};
+
+/**
+ * @brief Helper variable template.
+ * @tparam Type The type of which to return the size.
+ */
+template<typename Type>
+inline constexpr std::size_t size_of_v = size_of<Type>::value;
+
+/**
+ * @brief Using declaration to be used to _repeat_ the same type a number of
+ * times equal to the size of a given parameter pack.
+ * @tparam Type A type to repeat.
+ */
+template<typename Type, typename>
+using unpack_as_type = Type;
+
+/**
+ * @brief Helper variable template to be used to _repeat_ the same value a
+ * number of times equal to the size of a given parameter pack.
+ * @tparam Value A value to repeat.
+ */
+template<auto Value, typename>
+inline constexpr auto unpack_as_value = Value;
+
+/**
+ * @brief Wraps a static constant.
+ * @tparam Value A static constant.
+ */
+template<auto Value>
+using integral_constant = std::integral_constant<decltype(Value), Value>;
+
+/**
+ * @brief Alias template to facilitate the creation of named values.
+ * @tparam Value A constant value at least convertible to `id_type`.
+ */
+template<id_type Value>
+using tag = integral_constant<Value>;
+
+/**
+ * @brief A class to use to push around lists of types, nothing more.
+ * @tparam Type Types provided by the type list.
+ */
+template<typename... Type>
+struct type_list {
+    /*! @brief Type list type. */
+    using type = type_list;
+    /*! @brief Compile-time number of elements in the type list. */
+    static constexpr auto size = sizeof...(Type);
+};
+
+/*! @brief Primary template isn't defined on purpose. */
+template<std::size_t, typename>
+struct type_list_element;
+
+/**
+ * @brief Provides compile-time indexed access to the types of a type list.
+ * @tparam Index Index of the type to return.
+ * @tparam Type First type provided by the type list.
+ * @tparam Other Other types provided by the type list.
+ */
+template<std::size_t Index, typename Type, typename... Other>
+struct type_list_element<Index, type_list<Type, Other...>>
+    : type_list_element<Index - 1u, type_list<Other...>> {};
+
+/**
+ * @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<typename Type, typename... Other>
+struct type_list_element<0u, type_list<Type, Other...>> {
+    /*! @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<std::size_t Index, typename List>
+using type_list_element_t = typename type_list_element<Index, List>::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<typename... Type, typename... Other>
+constexpr type_list<Type..., Other...> operator+(type_list<Type...>, type_list<Other...>) {
+    return {};
+}
+
+/*! @brief Primary template isn't defined on purpose. */
+template<typename...>
+struct type_list_cat;
+
+/*! @brief Concatenates multiple type lists. */
+template<>
+struct type_list_cat<> {
+    /*! @brief A type list composed by the types of all the type lists. */
+    using type = type_list<>;
+};
+
+/**
+ * @brief Concatenates multiple type lists.
+ * @tparam Type Types provided by the first type list.
+ * @tparam Other Types provided by the second type list.
+ * @tparam List Other type lists, if any.
+ */
+template<typename... Type, typename... Other, typename... List>
+struct type_list_cat<type_list<Type...>, type_list<Other...>, List...> {
+    /*! @brief A type list composed by the types of all the type lists. */
+    using type = typename type_list_cat<type_list<Type..., Other...>, List...>::type;
+};
+
+/**
+ * @brief Concatenates multiple type lists.
+ * @tparam Type Types provided by the type list.
+ */
+template<typename... Type>
+struct type_list_cat<type_list<Type...>> {
+    /*! @brief A type list composed by the types of all the type lists. */
+    using type = type_list<Type...>;
+};
+
+/**
+ * @brief Helper type.
+ * @tparam List Type lists to concatenate.
+ */
+template<typename... List>
+using type_list_cat_t = typename type_list_cat<List...>::type;
+
+/*! @brief Primary template isn't defined on purpose. */
+template<typename>
+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<typename Type, typename... Other>
+struct type_list_unique<type_list<Type, Other...>> {
+    /*! @brief A type list without duplicate types. */
+    using type = std::conditional_t<
+        std::disjunction_v<std::is_same<Type, Other>...>,
+        typename type_list_unique<type_list<Other...>>::type,
+        type_list_cat_t<type_list<Type>, typename type_list_unique<type_list<Other...>>::type>>;
+};
+
+/*! @brief Removes duplicates types from a type list. */
+template<>
+struct type_list_unique<type_list<>> {
+    /*! @brief A type list without duplicate types. */
+    using type = type_list<>;
+};
+
+/**
+ * @brief Helper type.
+ * @tparam Type A type list.
+ */
+template<typename Type>
+using type_list_unique_t = typename type_list_unique<Type>::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<typename List, typename Type>
+struct type_list_contains;
+
+/**
+ * @copybrief type_list_contains
+ * @tparam Type Types provided by the type list.
+ * @tparam Other Type to look for.
+ */
+template<typename... Type, typename Other>
+struct type_list_contains<type_list<Type...>, Other>: std::disjunction<std::is_same<Type, Other>...> {};
+
+/**
+ * @brief Helper variable template.
+ * @tparam List Type list.
+ * @tparam Type Type to look for.
+ */
+template<typename List, typename Type>
+inline constexpr bool type_list_contains_v = type_list_contains<List, Type>::value;
+
+/*! @brief Primary template isn't defined on purpose. */
+template<typename...>
+struct type_list_diff;
+
+/**
+ * @brief Computes the difference between two type lists.
+ * @tparam Type Types provided by the first type list.
+ * @tparam Other Types provided by the second type list.
+ */
+template<typename... Type, typename... Other>
+struct type_list_diff<type_list<Type...>, type_list<Other...>> {
+    /*! @brief A type list that is the difference between the two type lists. */
+    using type = type_list_cat_t<std::conditional_t<type_list_contains_v<type_list<Other...>, Type>, type_list<>, type_list<Type>>...>;
+};
+
+/**
+ * @brief Helper type.
+ * @tparam List Type lists between which to compute the difference.
+ */
+template<typename... List>
+using type_list_diff_t = typename type_list_diff<List...>::type;
+
+/**
+ * @brief A class to use to push around lists of constant values, nothing more.
+ * @tparam Value Values provided by the value list.
+ */
+template<auto... Value>
+struct value_list {
+    /*! @brief Value list type. */
+    using type = value_list;
+    /*! @brief Compile-time number of elements in the value list. */
+    static constexpr auto size = sizeof...(Value);
+};
+
+/*! @brief Primary template isn't defined on purpose. */
+template<std::size_t, typename>
+struct value_list_element;
+
+/**
+ * @brief Provides compile-time indexed access to the values of a value list.
+ * @tparam Index Index of the value to return.
+ * @tparam Value First value provided by the value list.
+ * @tparam Other Other values provided by the value list.
+ */
+template<std::size_t Index, auto Value, auto... Other>
+struct value_list_element<Index, value_list<Value, Other...>>
+    : value_list_element<Index - 1u, value_list<Other...>> {};
+
+/**
+ * @brief Provides compile-time indexed access to the types of a type list.
+ * @tparam Value First value provided by the value list.
+ * @tparam Other Other values provided by the value list.
+ */
+template<auto Value, auto... Other>
+struct value_list_element<0u, value_list<Value, Other...>> {
+    /*! @brief Searched 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<std::size_t Index, typename List>
+inline constexpr auto value_list_element_v = value_list_element<Index, List>::value;
+
+/**
+ * @brief Concatenates multiple value lists.
+ * @tparam Value Values provided by the first value list.
+ * @tparam Other Values provided by the second value list.
+ * @return A value list composed by the values of both the value lists.
+ */
+template<auto... Value, auto... Other>
+constexpr value_list<Value..., Other...> operator+(value_list<Value...>, value_list<Other...>) {
+    return {};
+}
+
+/*! @brief Primary template isn't defined on purpose. */
+template<typename...>
+struct value_list_cat;
+
+/*! @brief Concatenates multiple value lists. */
+template<>
+struct value_list_cat<> {
+    /*! @brief A value list composed by the values of all the value lists. */
+    using type = value_list<>;
+};
+
+/**
+ * @brief Concatenates multiple value lists.
+ * @tparam Value Values provided by the first value list.
+ * @tparam Other Values provided by the second value list.
+ * @tparam List Other value lists, if any.
+ */
+template<auto... Value, auto... Other, typename... List>
+struct value_list_cat<value_list<Value...>, value_list<Other...>, List...> {
+    /*! @brief A value list composed by the values of all the value lists. */
+    using type = typename value_list_cat<value_list<Value..., Other...>, List...>::type;
+};
+
+/**
+ * @brief Concatenates multiple value lists.
+ * @tparam Value Values provided by the value list.
+ */
+template<auto... Value>
+struct value_list_cat<value_list<Value...>> {
+    /*! @brief A value list composed by the values of all the value lists. */
+    using type = value_list<Value...>;
+};
+
+/**
+ * @brief Helper type.
+ * @tparam List Value lists to concatenate.
+ */
+template<typename... List>
+using value_list_cat_t = typename value_list_cat<List...>::type;
+
+/*! @brief Same as std::is_invocable, but with tuples. */
+template<typename, typename>
+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<typename Func, template<typename...> class Tuple, typename... Args>
+struct is_applicable<Func, Tuple<Args...>>: std::is_invocable<Func, Args...> {};
+
+/**
+ * @copybrief is_applicable
+ * @tparam Func A valid function type.
+ * @tparam Tuple Tuple-like type.
+ * @tparam Args The list of arguments to use to probe the function type.
+ */
+template<typename Func, template<typename...> class Tuple, typename... Args>
+struct is_applicable<Func, const Tuple<Args...>>: std::is_invocable<Func, Args...> {};
+
+/**
+ * @brief Helper variable template.
+ * @tparam Func A valid function type.
+ * @tparam Args The list of arguments to use to probe the function type.
+ */
+template<typename Func, typename Args>
+inline constexpr bool is_applicable_v = is_applicable<Func, Args>::value;
+
+/*! @brief Same as std::is_invocable_r, but with tuples for arguments. */
+template<typename, typename, typename>
+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<typename Ret, typename Func, typename... Args>
+struct is_applicable_r<Ret, Func, std::tuple<Args...>>: std::is_invocable_r<Ret, Func, Args...> {};
+
+/**
+ * @brief Helper variable template.
+ * @tparam Ret The type to which the return type of the function should be
+ * convertible.
+ * @tparam Func A valid function type.
+ * @tparam Args The list of arguments to use to probe the function type.
+ */
+template<typename Ret, typename Func, typename Args>
+inline constexpr bool is_applicable_r_v = is_applicable_r<Ret, Func, Args>::value;
+
+/**
+ * @brief Provides the member constant `value` to true if a given type is
+ * complete, false otherwise.
+ * @tparam Type The type to test.
+ */
+template<typename Type, typename = void>
+struct is_complete: std::false_type {};
+
+/*! @copydoc is_complete */
+template<typename Type>
+struct is_complete<Type, std::void_t<decltype(sizeof(Type))>>: std::true_type {};
+
+/**
+ * @brief Helper variable template.
+ * @tparam Type The type to test.
+ */
+template<typename Type>
+inline constexpr bool is_complete_v = is_complete<Type>::value;
+
+/**
+ * @brief Provides the member constant `value` to true if a given type is an
+ * iterator, false otherwise.
+ * @tparam Type The type to test.
+ */
+template<typename Type, typename = void>
+struct is_iterator: std::false_type {};
+
+/**
+ * @cond TURN_OFF_DOXYGEN
+ * Internal details not to be documented.
+ */
+
+namespace internal {
+
+template<typename, typename = void>
+struct has_iterator_category: std::false_type {};
+
+template<typename Type>
+struct has_iterator_category<Type, std::void_t<typename std::iterator_traits<Type>::iterator_category>>: std::true_type {};
+
+} // namespace internal
+
+/**
+ * Internal details not to be documented.
+ * @endcond
+ */
+
+/*! @copydoc is_iterator */
+template<typename Type>
+struct is_iterator<Type, std::enable_if_t<!std::is_same_v<std::remove_const_t<std::remove_pointer_t<Type>>, void>>>
+    : internal::has_iterator_category<Type> {};
+
+/**
+ * @brief Helper variable template.
+ * @tparam Type The type to test.
+ */
+template<typename Type>
+inline constexpr bool is_iterator_v = is_iterator<Type>::value;
+
+/**
+ * @brief Provides the member constant `value` to true if a given type is of the
+ * required iterator type, false otherwise.
+ * @tparam Type The type to test.
+ * @tparam It Required iterator type.
+ */
+template<typename Type, typename It, typename = void>
+struct is_iterator_type: std::false_type {};
+
+/*! @copydoc is_iterator_type */
+template<typename Type, typename It>
+struct is_iterator_type<Type, It, std::enable_if_t<is_iterator_v<Type> && std::is_same_v<Type, It>>>
+    : std::true_type {};
+
+/*! @copydoc is_iterator_type */
+template<typename Type, typename It>
+struct is_iterator_type<Type, It, std::enable_if_t<!std::is_same_v<Type, It>, std::void_t<typename It::iterator_type>>>
+    : is_iterator_type<Type, typename It::iterator_type> {};
+
+/**
+ * @brief Helper variable template.
+ * @tparam Type The type to test.
+ * @tparam It Required iterator type.
+ */
+template<typename Type, typename It>
+inline constexpr bool is_iterator_type_v = is_iterator_type<Type, It>::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<typename Type>
+struct is_ebco_eligible
+    : std::conjunction<std::is_empty<Type>, std::negation<std::is_final<Type>>> {};
+
+/**
+ * @brief Helper variable template.
+ * @tparam Type The type to test.
+ */
+template<typename Type>
+inline constexpr bool is_ebco_eligible_v = is_ebco_eligible<Type>::value;
+
+/**
+ * @brief Provides the member constant `value` to true if `Type::is_transparent`
+ * is valid and denotes a type, false otherwise.
+ * @tparam Type The type to test.
+ */
+template<typename Type, typename = void>
+struct is_transparent: std::false_type {};
+
+/*! @copydoc is_transparent */
+template<typename Type>
+struct is_transparent<Type, std::void_t<typename Type::is_transparent>>: std::true_type {};
+
+/**
+ * @brief Helper variable template.
+ * @tparam Type The type to test.
+ */
+template<typename Type>
+inline constexpr bool is_transparent_v = is_transparent<Type>::value;
+
+/**
+ * @brief Provides the member constant `value` to true if a given type is
+ * equality comparable, false otherwise.
+ * @tparam Type The type to test.
+ */
+template<typename Type, typename = void>
+struct is_equality_comparable: std::false_type {};
+
+/**
+ * @cond TURN_OFF_DOXYGEN
+ * Internal details not to be documented.
+ */
+
+namespace internal {
+
+template<typename, typename = void>
+struct has_tuple_size_value: std::false_type {};
+
+template<typename Type>
+struct has_tuple_size_value<Type, std::void_t<decltype(std::tuple_size<const Type>::value)>>: std::true_type {};
+
+template<typename Type, std::size_t... Index>
+[[nodiscard]] constexpr bool unpack_maybe_equality_comparable(std::index_sequence<Index...>) {
+    return (is_equality_comparable<std::tuple_element_t<Index, Type>>::value && ...);
+}
+
+template<typename>
+[[nodiscard]] constexpr bool maybe_equality_comparable(choice_t<0>) {
+    return true;
+}
+
+template<typename Type>
+[[nodiscard]] constexpr auto maybe_equality_comparable(choice_t<1>) -> decltype(std::declval<typename Type::value_type>(), bool{}) {
+    if constexpr(is_iterator_v<Type>) {
+        return true;
+    } else if constexpr(std::is_same_v<typename Type::value_type, Type>) {
+        return maybe_equality_comparable<Type>(choice<0>);
+    } else {
+        return is_equality_comparable<typename Type::value_type>::value;
+    }
+}
+
+template<typename Type>
+[[nodiscard]] constexpr std::enable_if_t<is_complete_v<std::tuple_size<std::remove_const_t<Type>>>, bool> maybe_equality_comparable(choice_t<2>) {
+    if constexpr(has_tuple_size_value<Type>::value) {
+        return unpack_maybe_equality_comparable<Type>(std::make_index_sequence<std::tuple_size<Type>::value>{});
+    } else {
+        return maybe_equality_comparable<Type>(choice<1>);
+    }
+}
+
+} // namespace internal
+
+/**
+ * Internal details not to be documented.
+ * @endcond
+ */
+
+/*! @copydoc is_equality_comparable */
+template<typename Type>
+struct is_equality_comparable<Type, std::void_t<decltype(std::declval<Type>() == std::declval<Type>())>>
+    : std::bool_constant<internal::maybe_equality_comparable<Type>(choice<2>)> {};
+
+/**
+ * @brief Helper variable template.
+ * @tparam Type The type to test.
+ */
+template<typename Type>
+inline constexpr bool is_equality_comparable_v = is_equality_comparable<Type>::value;
+
+/**
+ * @brief Transcribes the constness of a type to another type.
+ * @tparam To The type to which to transcribe the constness.
+ * @tparam From The type from which to transcribe the constness.
+ */
+template<typename To, typename From>
+struct constness_as {
+    /*! @brief The type resulting from the transcription of the constness. */
+    using type = std::remove_const_t<To>;
+};
+
+/*! @copydoc constness_as */
+template<typename To, typename From>
+struct constness_as<To, const From> {
+    /*! @brief The type resulting from the transcription of the constness. */
+    using type = std::add_const_t<To>;
+};
+
+/**
+ * @brief Alias template to facilitate the transcription of the constness.
+ * @tparam To The type to which to transcribe the constness.
+ * @tparam From The type from which to transcribe the constness.
+ */
+template<typename To, typename From>
+using constness_as_t = typename constness_as<To, From>::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<typename Member>
+class member_class {
+    static_assert(std::is_member_pointer_v<Member>, "Invalid pointer type to non-static member object or function");
+
+    template<typename Class, typename Ret, typename... Args>
+    static Class *clazz(Ret (Class::*)(Args...));
+
+    template<typename Class, typename Ret, typename... Args>
+    static Class *clazz(Ret (Class::*)(Args...) const);
+
+    template<typename Class, typename Type>
+    static Class *clazz(Type Class::*);
+
+public:
+    /*! @brief The class of the given non-static member object or function. */
+    using type = std::remove_pointer_t<decltype(clazz(std::declval<Member>()))>;
+};
+
+/**
+ * @brief Helper type.
+ * @tparam Member A pointer to a non-static member object or function.
+ */
+template<typename Member>
+using member_class_t = typename member_class<Member>::type;
+
+} // namespace entt
+
+#endif

+ 101 - 0
Dependencies/include/entt/core/utility.hpp

@@ -0,0 +1,101 @@
+#ifndef ENTT_CORE_UTILITY_HPP
+#define ENTT_CORE_UTILITY_HPP
+
+#include <utility>
+#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<class Type>
+    [[nodiscard]] constexpr Type &&operator()(Type &&value) const ENTT_NOEXCEPT {
+        return std::forward<Type>(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<typename Type, typename Class>
+[[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<typename Func>
+[[nodiscard]] constexpr auto overload(Func *func) ENTT_NOEXCEPT {
+    return func;
+}
+
+/**
+ * @brief Helper type for visitors.
+ * @tparam Func Types of function objects.
+ */
+template<class... Func>
+struct overloaded: Func... {
+    using Func::operator()...;
+};
+
+/**
+ * @brief Deduction guide.
+ * @tparam Func Types of function objects.
+ */
+template<class... Func>
+overloaded(Func...) -> overloaded<Func...>;
+
+/**
+ * @brief Basic implementation of a y-combinator.
+ * @tparam Func Type of a potentially recursive function.
+ */
+template<class Func>
+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<class... Args>
+    decltype(auto) operator()(Args &&...args) const {
+        return func(*this, std::forward<Args>(args)...);
+    }
+
+    /*! @copydoc operator()() */
+    template<class... Args>
+    decltype(auto) operator()(Args &&...args) {
+        return func(*this, std::forward<Args>(args)...);
+    }
+
+private:
+    Func func;
+};
+
+} // namespace entt
+
+#endif

+ 37 - 0
Dependencies/include/entt/entity/component.hpp

@@ -0,0 +1,37 @@
+#ifndef ENTT_ENTITY_COMPONENT_HPP
+#define ENTT_ENTITY_COMPONENT_HPP
+
+#include <type_traits>
+#include "../config/config.h"
+
+namespace entt {
+
+/*! @brief Commonly used default traits for all types. */
+struct basic_component_traits {
+    /*! @brief Pointer stability, default is `false`. */
+    static constexpr auto in_place_delete = true;
+    /*! @brief Empty type optimization, default is `ENTT_IGNORE_IF_EMPTY`. */
+    static constexpr auto ignore_if_empty = ENTT_IGNORE_IF_EMPTY;
+    /*! @brief Page size, default is `ENTT_PACKED_PAGE`. */
+    static constexpr auto page_size = ENTT_PACKED_PAGE;
+};
+
+/**
+ * @brief Common way to access various properties of components.
+ * @tparam Type Type of component.
+ */
+template<typename Type, typename = void>
+struct component_traits: basic_component_traits {
+    static_assert(std::is_same_v<std::decay_t<Type>, Type>, "Unsupported type");
+};
+
+/**
+ * @brief Helper variable template.
+ * @tparam Type Type of component.
+ */
+template<class Type>
+inline constexpr bool ignore_as_empty_v = component_traits<Type>::ignore_if_empty &&std::is_empty_v<Type>;
+
+} // namespace entt
+
+#endif

+ 339 - 0
Dependencies/include/entt/entity/entity.hpp

@@ -0,0 +1,339 @@
+#ifndef ENTT_ENTITY_ENTITY_HPP
+#define ENTT_ENTITY_ENTITY_HPP
+
+#include <cstddef>
+#include <cstdint>
+#include <type_traits>
+#include "../config/config.h"
+#include "fwd.hpp"
+
+namespace entt {
+
+/**
+ * @cond TURN_OFF_DOXYGEN
+ * Internal details not to be documented.
+ */
+
+namespace internal {
+
+template<typename, typename = void>
+struct entt_traits;
+
+template<typename Type>
+struct entt_traits<Type, std::enable_if_t<std::is_enum_v<Type>>>
+    : entt_traits<std::underlying_type_t<Type>> {};
+
+template<typename Type>
+struct entt_traits<Type, std::enable_if_t<std::is_class_v<Type>>>
+    : entt_traits<typename Type::entity_type> {};
+
+template<>
+struct entt_traits<std::uint32_t> {
+    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<std::uint64_t> {
+    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<typename Type>
+class entt_traits: internal::entt_traits<Type> {
+    using base_type = internal::entt_traits<Type>;
+
+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<entity_type>(value);
+    }
+
+    /**
+     * @brief Returns the entity part once converted to the underlying type.
+     * @param value The value to convert.
+     * @return The integral representation of the entity part.
+     */
+    [[nodiscard]] static constexpr entity_type to_entity(const value_type value) 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.<br/>
+     * If the entity part is not provided, a null identifier is returned.
+     *
+     * @param entity The entity part of the identifier.
+     * @param version The version part of the identifier.
+     * @return A properly constructed identifier.
+     */
+    [[nodiscard]] static constexpr value_type construct(const entity_type entity, const version_type version) ENTT_NOEXCEPT {
+        return value_type{(entity & base_type::entity_mask) | (static_cast<entity_type>(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<Entity>::to_integral
+ * @tparam Entity The value type.
+ */
+template<typename Entity>
+[[nodiscard]] constexpr typename entt_traits<Entity>::entity_type to_integral(const Entity value) ENTT_NOEXCEPT {
+    return entt_traits<Entity>::to_integral(value);
+}
+
+/**
+ * @copydoc entt_traits<Entity>::to_entity
+ * @tparam Entity The value type.
+ */
+template<typename Entity>
+[[nodiscard]] constexpr typename entt_traits<Entity>::entity_type to_entity(const Entity value) ENTT_NOEXCEPT {
+    return entt_traits<Entity>::to_entity(value);
+}
+
+/**
+ * @copydoc entt_traits<Entity>::to_version
+ * @tparam Entity The value type.
+ */
+template<typename Entity>
+[[nodiscard]] constexpr typename entt_traits<Entity>::version_type to_version(const Entity value) ENTT_NOEXCEPT {
+    return entt_traits<Entity>::to_version(value);
+}
+
+/*! @brief Null object for all identifiers.  */
+struct null_t {
+    /**
+     * @brief Converts the null object to identifiers of any type.
+     * @tparam Entity Type of identifier.
+     * @return The null representation for the given type.
+     */
+    template<typename Entity>
+    [[nodiscard]] constexpr operator Entity() const ENTT_NOEXCEPT {
+        using entity_traits = entt_traits<Entity>;
+        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<typename Entity>
+    [[nodiscard]] constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT {
+        using entity_traits = entt_traits<Entity>;
+        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<typename Entity>
+    [[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<typename 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<typename 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<typename Entity>
+    [[nodiscard]] constexpr operator Entity() const ENTT_NOEXCEPT {
+        using entity_traits = entt_traits<Entity>;
+        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<typename Entity>
+    [[nodiscard]] constexpr bool operator==(const Entity entity) const ENTT_NOEXCEPT {
+        using entity_traits = entt_traits<Entity>;
+        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<typename Entity>
+    [[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<typename Entity>
+[[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<typename Entity>
+[[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 comparision 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 comparision operators between the
+ * tombstone entity and any other identifier.
+ */
+inline constexpr tombstone_t tombstone{};
+
+} // namespace entt
+
+#endif

+ 117 - 0
Dependencies/include/entt/entity/fwd.hpp

@@ -0,0 +1,117 @@
+#ifndef ENTT_ENTITY_FWD_HPP
+#define ENTT_ENTITY_FWD_HPP
+
+#include <memory>
+#include "../core/fwd.hpp"
+#include "utility.hpp"
+
+namespace entt {
+
+template<typename Entity, typename = std::allocator<Entity>>
+class basic_sparse_set;
+
+template<typename, typename Type, typename = std::allocator<Type>, typename = void>
+class basic_storage;
+
+template<typename>
+class basic_registry;
+
+template<typename, typename, typename, typename = void>
+class basic_view;
+
+template<typename>
+struct basic_runtime_view;
+
+template<typename, typename, typename, typename>
+class basic_group;
+
+template<typename>
+class basic_observer;
+
+template<typename>
+class basic_organizer;
+
+template<typename, typename...>
+struct basic_handle;
+
+template<typename>
+class basic_snapshot;
+
+template<typename>
+class basic_snapshot_loader;
+
+template<typename>
+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<entity>;
+
+/**
+ * @brief Alias declaration for the most common use case.
+ * @tparam Args Other template parameters.
+ */
+template<typename... Args>
+using storage = basic_storage<entity, Args...>;
+
+/*! @brief Alias declaration for the most common use case. */
+using registry = basic_registry<entity>;
+
+/*! @brief Alias declaration for the most common use case. */
+using observer = basic_observer<entity>;
+
+/*! @brief Alias declaration for the most common use case. */
+using organizer = basic_organizer<entity>;
+
+/*! @brief Alias declaration for the most common use case. */
+using handle = basic_handle<entity>;
+
+/*! @brief Alias declaration for the most common use case. */
+using const_handle = basic_handle<const entity>;
+
+/**
+ * @brief Alias declaration for the most common use case.
+ * @tparam Args Other template parameters.
+ */
+template<typename... Args>
+using handle_view = basic_handle<entity, Args...>;
+
+/**
+ * @brief Alias declaration for the most common use case.
+ * @tparam Args Other template parameters.
+ */
+template<typename... Args>
+using const_handle_view = basic_handle<const entity, Args...>;
+
+/*! @brief Alias declaration for the most common use case. */
+using snapshot = basic_snapshot<entity>;
+
+/*! @brief Alias declaration for the most common use case. */
+using snapshot_loader = basic_snapshot_loader<entity>;
+
+/*! @brief Alias declaration for the most common use case. */
+using continuous_loader = basic_continuous_loader<entity>;
+
+/**
+ * @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<typename Get, typename Exclude = exclude_t<>>
+using view = basic_view<entity, Get, Exclude>;
+
+/*! @brief Alias declaration for the most common use case. */
+using runtime_view = basic_runtime_view<sparse_set>;
+
+/**
+ * @brief Alias declaration for the most common use case.
+ * @tparam Args Other template parameters.
+ */
+template<typename... Args>
+using group = basic_group<entity, Args...>;
+
+} // namespace entt
+
+#endif

+ 904 - 0
Dependencies/include/entt/entity/group.hpp

@@ -0,0 +1,904 @@
+#ifndef ENTT_ENTITY_GROUP_HPP
+#define ENTT_ENTITY_GROUP_HPP
+
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#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<typename, typename, typename, typename>
+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.<br/>
+ * 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<typename Entity, typename... Get, typename... Exclude>
+class basic_group<Entity, owned_t<>, get_t<Get...>, exclude_t<Exclude...>> final {
+    /*! @brief A registry is allowed to create groups. */
+    friend class basic_registry<Entity>;
+
+    template<typename Comp>
+    using storage_type = constness_as_t<typename storage_traits<Entity, std::remove_const_t<Comp>>::storage_type, Comp>;
+
+    using basic_common_type = std::common_type_t<typename storage_type<Get>::base_type...>;
+
+    struct extended_group_iterator final {
+        using difference_type = std::ptrdiff_t;
+        using value_type = decltype(std::tuple_cat(std::tuple<Entity>{}, std::declval<basic_group>().get({})));
+        using pointer = input_iterator_pointer<value_type>;
+        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<storage_type<Get> *...> &args) ENTT_NOEXCEPT
+            : 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<storage_type<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<storage_type<Get> *...> pools;
+    };
+
+    basic_group(basic_common_type &ref, storage_type<Get> &...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<extended_group_iterator>;
+
+    /*! @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<typename Comp>
+    [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT {
+        return *std::get<storage_type<Comp> *>(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<std::size_t Comp>
+    [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT {
+        return *std::get<Comp>(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 {
+        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 {
+        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 {
+        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 {
+        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<typename... Comp>
+    [[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<storage_type<Get> *>(pools)->get_as_tuple(entt)...);
+        } else if constexpr(sizeof...(Comp) == 1) {
+            return (std::get<storage_type<Comp> *>(pools)->get(entt), ...);
+        } else {
+            return std::tuple_cat(std::get<storage_type<Comp> *>(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.<br/>
+     * 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<typename Func>
+    void each(Func func) const {
+        for(const auto entt: *this) {
+            if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_group>().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<Component &...>, std::tuple<Component &...>);
+     * bool(const Component &..., const Component &...);
+     * bool(const Entity, const Entity);
+     * @endcode
+     *
+     * Where `Component` are such that they are iterated by the group.<br/>
+     * 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<typename... Comp, typename Compare, typename Sort = std_sort, typename... Args>
+    void sort(Compare compare, Sort algo = Sort{}, Args &&...args) {
+        if(*this) {
+            if constexpr(sizeof...(Comp) == 0) {
+                static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function");
+                handler->sort(std::move(compare), std::move(algo), std::forward<Args>(args)...);
+            } else {
+                auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) {
+                    if constexpr(sizeof...(Comp) == 1) {
+                        return compare((std::get<storage_type<Comp> *>(pools)->get(lhs), ...), (std::get<storage_type<Comp> *>(pools)->get(rhs), ...));
+                    } else {
+                        return compare(std::forward_as_tuple(std::get<storage_type<Comp> *>(pools)->get(lhs)...), std::forward_as_tuple(std::get<storage_type<Comp> *>(pools)->get(rhs)...));
+                    }
+                };
+
+                handler->sort(std::move(comp), std::move(algo), std::forward<Args>(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<typename Comp>
+    void sort() const {
+        if(*this) {
+            handler->respect(*std::get<storage_type<Comp> *>(pools));
+        }
+    }
+
+private:
+    base_type *const handler;
+    const std::tuple<storage_type<Get> *...> 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<typename Entity, typename... Owned, typename... Get, typename... Exclude>
+class basic_group<Entity, owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>> final {
+    /*! @brief A registry is allowed to create groups. */
+    friend class basic_registry<Entity>;
+
+    template<typename Comp>
+    using storage_type = constness_as_t<typename storage_traits<Entity, std::remove_const_t<Comp>>::storage_type, Comp>;
+
+    using basic_common_type = std::common_type_t<typename storage_type<Owned>::base_type..., typename storage_type<Get>::base_type...>;
+
+    template<typename>
+    struct extended_group_iterator;
+
+    template<typename... OIt>
+    struct extended_group_iterator<type_list<OIt...>> final {
+        using difference_type = std::ptrdiff_t;
+        using value_type = decltype(std::tuple_cat(std::tuple<Entity>{}, std::declval<basic_group>().get({})));
+        using pointer = input_iterator_pointer<value_type>;
+        using reference = value_type;
+        using iterator_category = std::input_iterator_tag;
+
+        extended_group_iterator() = default;
+
+        template<typename... Other>
+        extended_group_iterator(typename basic_common_type::iterator from, const std::tuple<Other...> &other, const std::tuple<storage_type<Get> *...> &cpools) ENTT_NOEXCEPT
+            : it{from},
+              owned{std::get<OIt>(other)...},
+              get{cpools} {}
+
+        extended_group_iterator &operator++() ENTT_NOEXCEPT {
+            return ++it, (++std::get<OIt>(owned), ...), *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),
+                std::forward_as_tuple(*std::get<OIt>(owned)...),
+                std::get<storage_type<Get> *>(get)->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<OIt...> owned;
+        std::tuple<storage_type<Get> *...> get;
+    };
+
+    basic_group(const std::size_t &extent, storage_type<Owned> &...opool, storage_type<Get> &...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<extended_group_iterator<type_list_cat_t<std::conditional_t<ignore_as_empty_v<std::remove_const_t<Owned>>, type_list<>, type_list<decltype(std::declval<storage_type<Owned>>().end())>>...>>>;
+
+    /*! @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<typename Comp>
+    [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT {
+        return *std::get<storage_type<Comp> *>(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<std::size_t Comp>
+    [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT {
+        return *std::get<Comp>(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 {
+        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 {
+        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 {
+        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 {
+        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<typename... Comp>
+    [[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<storage_type<Owned> *>(pools)->get_as_tuple(entt)..., std::get<storage_type<Get> *>(pools)->get_as_tuple(entt)...);
+        } else if constexpr(sizeof...(Comp) == 1) {
+            return (std::get<storage_type<Comp> *>(pools)->get(entt), ...);
+        } else {
+            return std::tuple_cat(std::get<storage_type<Comp> *>(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.<br/>
+     * 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<typename Func>
+    void each(Func func) const {
+        for(auto args: each()) {
+            if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_group>().get({})))>) {
+                std::apply(func, args);
+            } else {
+                std::apply([&func](auto, auto &&...less) { func(std::forward<decltype(less)>(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 {
+        using extended_iterator_type = typename iterable::iterator;
+        iterator last = length ? std::get<0>(pools)->basic_common_type::end() : iterator{};
+        auto from = extended_iterator_type{last - *length, std::make_tuple((std::get<storage_type<Owned> *>(pools)->end() - *length)...), std::make_tuple(std::get<storage_type<Get> *>(pools)...)};
+        auto to = extended_iterator_type{last, std::make_tuple((std::get<storage_type<Owned> *>(pools)->end())...), std::make_tuple(std::get<storage_type<Get> *>(pools)...)};
+        return {std::move(from), std::move(to)};
+    }
+
+    /**
+     * @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<Component &...>, std::tuple<Component &...>);
+     * 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.<br/>
+     * 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<typename... Comp, typename Compare, typename Sort = std_sort, typename... Args>
+    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<Compare, const entity_type, const entity_type>, "Invalid comparison function");
+            cpool->sort_n(*length, std::move(compare), std::move(algo), std::forward<Args>(args)...);
+        } else {
+            auto comp = [this, &compare](const entity_type lhs, const entity_type rhs) {
+                if constexpr(sizeof...(Comp) == 1) {
+                    return compare((std::get<storage_type<Comp> *>(pools)->get(lhs), ...), (std::get<storage_type<Comp> *>(pools)->get(rhs), ...));
+                } else {
+                    return compare(std::forward_as_tuple(std::get<storage_type<Comp> *>(pools)->get(lhs)...), std::forward_as_tuple(std::get<storage_type<Comp> *>(pools)->get(rhs)...));
+                }
+            };
+
+            cpool->sort_n(*length, std::move(comp), std::move(algo), std::forward<Args>(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<storage_type<Owned> *>(pools)...);
+    }
+
+private:
+    const std::tuple<storage_type<Owned> *..., storage_type<Get> *...> pools;
+    const size_type *const length;
+};
+
+} // namespace entt
+
+#endif

+ 340 - 0
Dependencies/include/entt/entity/handle.hpp

@@ -0,0 +1,340 @@
+#ifndef ENTT_ENTITY_HANDLE_HPP
+#define ENTT_ENTITY_HANDLE_HPP
+
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#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<typename Entity, typename... Type>
+struct basic_handle {
+    /*! @brief Type of registry accepted by the handle. */
+    using registry_type = constness_as_t<basic_registry<std::remove_const_t<Entity>>, 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<typename Other, typename... Args>
+    operator basic_handle<Other, Args...>() const ENTT_NOEXCEPT {
+        static_assert(std::is_same_v<Other, Entity> || std::is_same_v<std::remove_const_t<Other>, Entity>, "Invalid conversion between different handles");
+        static_assert((sizeof...(Type) == 0 || ((sizeof...(Args) != 0 && sizeof...(Args) <= sizeof...(Type)) && ... && (type_list_contains_v<type_list<Type...>, Args>))), "Invalid conversion between different handles");
+
+        return reg ? basic_handle<Other, Args...>{*reg, entt} : basic_handle<Other, Args...>{};
+    }
+
+    /**
+     * @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<typename Component, typename... Args>
+    decltype(auto) emplace(Args &&...args) const {
+        static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v<Component, Type>), "Invalid type");
+        return reg->template emplace<Component>(entt, std::forward<Args>(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<typename Component, typename... Args>
+    decltype(auto) emplace_or_replace(Args &&...args) const {
+        static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v<Component, Type>), "Invalid type");
+        return reg->template emplace_or_replace<Component>(entt, std::forward<Args>(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<typename Component, typename... Func>
+    decltype(auto) patch(Func &&...func) const {
+        static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v<Component, Type>), "Invalid type");
+        return reg->template patch<Component>(entt, std::forward<Func>(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<typename Component, typename... Args>
+    decltype(auto) replace(Args &&...args) const {
+        static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v<Component, Type>), "Invalid type");
+        return reg->template replace<Component>(entt, std::forward<Args>(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<typename... Component>
+    size_type remove() const {
+        static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type");
+        return reg->template remove<Component...>(entt);
+    }
+
+    /**
+     * @brief Erases the given components from a handle.
+     * @sa basic_registry::erase
+     * @tparam Component Types of components to erase.
+     */
+    template<typename... Component>
+    void erase() const {
+        static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type");
+        reg->template erase<Component...>(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<typename... Component>
+    [[nodiscard]] decltype(auto) all_of() const {
+        return reg->template all_of<Component...>(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<typename... Component>
+    [[nodiscard]] decltype(auto) any_of() const {
+        return reg->template any_of<Component...>(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<typename... Component>
+    [[nodiscard]] decltype(auto) get() const {
+        static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type");
+        return reg->template get<Component...>(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<typename Component, typename... Args>
+    [[nodiscard]] decltype(auto) get_or_emplace(Args &&...args) const {
+        static_assert(((sizeof...(Type) == 0) || ... || std::is_same_v<Component, Type>), "Invalid type");
+        return reg->template get_or_emplace<Component>(entt, std::forward<Args>(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<typename... Component>
+    [[nodiscard]] auto try_get() const {
+        static_assert(sizeof...(Type) == 0 || (type_list_contains_v<type_list<Type...>, Component> && ...), "Invalid type");
+        return reg->template try_get<Component...>(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<entity_type> &);
+     * @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<typename Func>
+    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<typename... Args, typename... Other>
+[[nodiscard]] bool operator==(const basic_handle<Args...> &lhs, const basic_handle<Other...> &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<typename... Args, typename... Other>
+[[nodiscard]] bool operator!=(const basic_handle<Args...> &lhs, const basic_handle<Other...> &rhs) ENTT_NOEXCEPT {
+    return !(lhs == rhs);
+}
+
+/**
+ * @brief Deduction guide.
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ */
+template<typename Entity>
+basic_handle(basic_registry<Entity> &, Entity) -> basic_handle<Entity>;
+
+/**
+ * @brief Deduction guide.
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ */
+template<typename Entity>
+basic_handle(const basic_registry<Entity> &, Entity) -> basic_handle<const Entity>;
+
+} // namespace entt
+
+#endif

+ 156 - 0
Dependencies/include/entt/entity/helper.hpp

@@ -0,0 +1,156 @@
+#ifndef ENTT_ENTITY_HELPER_HPP
+#define ENTT_ENTITY_HELPER_HPP
+
+#include <type_traits>
+#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<typename Entity>
+struct as_view {
+    /*! @brief Underlying entity identifier. */
+    using entity_type = std::remove_const_t<Entity>;
+    /*! @brief Type of registry to convert. */
+    using registry_type = constness_as_t<basic_registry<entity_type>, 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<typename Exclude, typename... Component>
+    operator basic_view<entity_type, get_t<Component...>, Exclude>() const {
+        return reg.template view<Component...>(Exclude{});
+    }
+
+private:
+    registry_type &reg;
+};
+
+/**
+ * @brief Deduction guide.
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ */
+template<typename Entity>
+as_view(basic_registry<Entity> &) -> as_view<Entity>;
+
+/**
+ * @brief Deduction guide.
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ */
+template<typename Entity>
+as_view(const basic_registry<Entity> &) -> as_view<const Entity>;
+
+/**
+ * @brief Converts a registry to a group.
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ */
+template<typename Entity>
+struct as_group {
+    /*! @brief Underlying entity identifier. */
+    using entity_type = std::remove_const_t<Entity>;
+    /*! @brief Type of registry to convert. */
+    using registry_type = constness_as_t<basic_registry<entity_type>, 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<typename Get, typename Exclude, typename... Owned>
+    operator basic_group<entity_type, owned_t<Owned...>, Get, Exclude>() const {
+        if constexpr(std::is_const_v<registry_type>) {
+            return reg.template group_if_exists<Owned...>(Get{}, Exclude{});
+        } else {
+            return reg.template group<Owned...>(Get{}, Exclude{});
+        }
+    }
+
+private:
+    registry_type &reg;
+};
+
+/**
+ * @brief Deduction guide.
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ */
+template<typename Entity>
+as_group(basic_registry<Entity> &) -> as_group<Entity>;
+
+/**
+ * @brief Deduction guide.
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ */
+template<typename Entity>
+as_group(const basic_registry<Entity> &) -> as_group<const Entity>;
+
+/**
+ * @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<auto Member, typename Entity = entity>
+void invoke(basic_registry<Entity> &reg, const Entity entt) {
+    static_assert(std::is_member_function_pointer_v<decltype(Member)>, "Invalid pointer to non-static member function");
+    delegate<void(basic_registry<Entity> &, const Entity)> func;
+    func.template connect<Member>(reg.template get<member_class_t<decltype(Member)>>(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<typename Entity, typename Component>
+Entity to_entity(const basic_registry<Entity> &reg, const Component &instance) {
+    const auto &storage = reg.template storage<Component>();
+    const typename basic_registry<Entity>::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

+ 436 - 0
Dependencies/include/entt/entity/observer.hpp

@@ -0,0 +1,436 @@
+#ifndef ENTT_ENTITY_OBSERVER_HPP
+#define ENTT_ENTITY_OBSERVER_HPP
+
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <type_traits>
+#include <utility>
+#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<typename...>
+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<typename...>
+struct basic_collector;
+
+/**
+ * @brief Collector.
+ *
+ * A collector contains a set of rules (literally, matchers) to use to track
+ * entities.<br/>
+ * 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<typename... AllOf, typename... NoneOf>
+    static constexpr auto group(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT {
+        return basic_collector<matcher<type_list<>, type_list<>, type_list<NoneOf...>, 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<typename AnyOf>
+    static constexpr auto update() ENTT_NOEXCEPT {
+        return basic_collector<matcher<type_list<>, 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<typename... Reject, typename... Require, typename... Rule, typename... Other>
+struct basic_collector<matcher<type_list<Reject...>, type_list<Require...>, Rule...>, Other...> {
+    /*! @brief Current matcher. */
+    using current_type = matcher<type_list<Reject...>, type_list<Require...>, 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<typename... AllOf, typename... NoneOf>
+    static constexpr auto group(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT {
+        return basic_collector<matcher<type_list<>, type_list<>, type_list<NoneOf...>, 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<typename AnyOf>
+    static constexpr auto update() ENTT_NOEXCEPT {
+        return basic_collector<matcher<type_list<>, 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<typename... AllOf, typename... NoneOf>
+    static constexpr auto where(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT {
+        using extended_type = matcher<type_list<Reject..., NoneOf...>, type_list<Require..., AllOf...>, Rule...>;
+        return basic_collector<extended_type, Other...>{};
+    }
+};
+
+/*! @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.<br/>
+ * 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.<br/>
+ * 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<typename Entity>
+class basic_observer {
+    using payload_type = std::uint32_t;
+
+    template<typename>
+    struct matcher_handler;
+
+    template<typename... Reject, typename... Require, typename AnyOf>
+    struct matcher_handler<matcher<type_list<Reject...>, type_list<Require...>, AnyOf>> {
+        template<std::size_t Index>
+        static void maybe_valid_if(basic_observer &obs, basic_registry<Entity> &reg, const Entity entt) {
+            if(reg.template all_of<Require...>(entt) && !reg.template any_of<Reject...>(entt)) {
+                if(!obs.storage.contains(entt)) {
+                    obs.storage.emplace(entt);
+                }
+
+                obs.storage.get(entt) |= (1 << Index);
+            }
+        }
+
+        template<std::size_t Index>
+        static void discard_if(basic_observer &obs, basic_registry<Entity> &, const Entity entt) {
+            if(obs.storage.contains(entt) && !(obs.storage.get(entt) &= (~(1 << Index)))) {
+                obs.storage.erase(entt);
+            }
+        }
+
+        template<std::size_t Index>
+        static void connect(basic_observer &obs, basic_registry<Entity> &reg) {
+            (reg.template on_destroy<Require>().template connect<&discard_if<Index>>(obs), ...);
+            (reg.template on_construct<Reject>().template connect<&discard_if<Index>>(obs), ...);
+            reg.template on_update<AnyOf>().template connect<&maybe_valid_if<Index>>(obs);
+            reg.template on_destroy<AnyOf>().template connect<&discard_if<Index>>(obs);
+        }
+
+        static void disconnect(basic_observer &obs, basic_registry<Entity> &reg) {
+            (reg.template on_destroy<Require>().disconnect(obs), ...);
+            (reg.template on_construct<Reject>().disconnect(obs), ...);
+            reg.template on_update<AnyOf>().disconnect(obs);
+            reg.template on_destroy<AnyOf>().disconnect(obs);
+        }
+    };
+
+    template<typename... Reject, typename... Require, typename... NoneOf, typename... AllOf>
+    struct matcher_handler<matcher<type_list<Reject...>, type_list<Require...>, type_list<NoneOf...>, AllOf...>> {
+        template<std::size_t Index, typename... Ignore>
+        static void maybe_valid_if(basic_observer &obs, basic_registry<Entity> &reg, const Entity entt) {
+            auto condition = [&reg, entt]() {
+                if constexpr(sizeof...(Ignore) == 0) {
+                    return reg.template all_of<AllOf..., Require...>(entt) && !reg.template any_of<NoneOf..., Reject...>(entt);
+                } else {
+                    return reg.template all_of<AllOf..., Require...>(entt) && ((std::is_same_v<Ignore..., NoneOf> || !reg.template any_of<NoneOf>(entt)) && ...) && !reg.template any_of<Reject...>(entt);
+                }
+            };
+
+            if(condition()) {
+                if(!obs.storage.contains(entt)) {
+                    obs.storage.emplace(entt);
+                }
+
+                obs.storage.get(entt) |= (1 << Index);
+            }
+        }
+
+        template<std::size_t Index>
+        static void discard_if(basic_observer &obs, basic_registry<Entity> &, const Entity entt) {
+            if(obs.storage.contains(entt) && !(obs.storage.get(entt) &= (~(1 << Index)))) {
+                obs.storage.erase(entt);
+            }
+        }
+
+        template<std::size_t Index>
+        static void connect(basic_observer &obs, basic_registry<Entity> &reg) {
+            (reg.template on_destroy<Require>().template connect<&discard_if<Index>>(obs), ...);
+            (reg.template on_construct<Reject>().template connect<&discard_if<Index>>(obs), ...);
+            (reg.template on_construct<AllOf>().template connect<&maybe_valid_if<Index>>(obs), ...);
+            (reg.template on_destroy<NoneOf>().template connect<&maybe_valid_if<Index, NoneOf>>(obs), ...);
+            (reg.template on_destroy<AllOf>().template connect<&discard_if<Index>>(obs), ...);
+            (reg.template on_construct<NoneOf>().template connect<&discard_if<Index>>(obs), ...);
+        }
+
+        static void disconnect(basic_observer &obs, basic_registry<Entity> &reg) {
+            (reg.template on_destroy<Require>().disconnect(obs), ...);
+            (reg.template on_construct<Reject>().disconnect(obs), ...);
+            (reg.template on_construct<AllOf>().disconnect(obs), ...);
+            (reg.template on_destroy<NoneOf>().disconnect(obs), ...);
+            (reg.template on_destroy<AllOf>().disconnect(obs), ...);
+            (reg.template on_construct<NoneOf>().disconnect(obs), ...);
+        }
+    };
+
+    template<typename... Matcher>
+    static void disconnect(basic_registry<Entity> &reg, basic_observer &obs) {
+        (matcher_handler<Matcher>::disconnect(obs, reg), ...);
+    }
+
+    template<typename... Matcher, std::size_t... Index>
+    void connect(basic_registry<Entity> &reg, std::index_sequence<Index...>) {
+        static_assert(sizeof...(Matcher) < std::numeric_limits<payload_type>::digits, "Too many matchers");
+        (matcher_handler<Matcher>::template connect<Index>(*this, reg), ...);
+        release.template connect<&basic_observer::disconnect<Matcher...>>(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<Entity>::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<typename... Matcher>
+    basic_observer(basic_registry<entity_type> &reg, basic_collector<Matcher...>)
+        : basic_observer{} {
+        connect<Matcher...>(reg, std::index_sequence_for<Matcher...>{});
+    }
+
+    /*! @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<typename... Matcher>
+    void connect(basic_registry<entity_type> &reg, basic_collector<Matcher...>) {
+        disconnect();
+        connect<Matcher...>(reg, std::index_sequence_for<Matcher...>{});
+        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<entity_type>::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<entity_type>::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.<br/>
+     * 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<typename Func>
+    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<typename Func>
+    void each(Func func) {
+        std::as_const(*this).each(std::move(func));
+        clear();
+    }
+
+private:
+    delegate<void(basic_observer &)> release;
+    basic_storage<entity_type, payload_type> storage;
+};
+
+} // namespace entt
+
+#endif

+ 487 - 0
Dependencies/include/entt/entity/organizer.hpp

@@ -0,0 +1,487 @@
+#ifndef ENTT_ENTITY_ORGANIZER_HPP
+#define ENTT_ENTITY_ORGANIZER_HPP
+
+#include <algorithm>
+#include <cstddef>
+#include <type_traits>
+#include <utility>
+#include <vector>
+#include "../container/dense_hash_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<typename>
+struct is_view: std::false_type {};
+
+template<typename Entity, typename... Component, typename... Exclude>
+struct is_view<basic_view<Entity, get_t<Component...>, exclude_t<Exclude...>>>: std::true_type {};
+
+template<typename Type>
+inline constexpr bool is_view_v = is_view<Type>::value;
+
+template<typename Type, typename Override>
+struct unpack_type {
+    using ro = std::conditional_t<
+        type_list_contains_v<Override, std::add_const_t<Type>> || (std::is_const_v<Type> && !type_list_contains_v<Override, std::remove_const_t<Type>>),
+        type_list<std::remove_const_t<Type>>,
+        type_list<>>;
+
+    using rw = std::conditional_t<
+        type_list_contains_v<Override, std::remove_const_t<Type>> || (!std::is_const_v<Type> && !type_list_contains_v<Override, std::add_const_t<Type>>),
+        type_list<Type>,
+        type_list<>>;
+};
+
+template<typename Entity, typename... Override>
+struct unpack_type<basic_registry<Entity>, type_list<Override...>> {
+    using ro = type_list<>;
+    using rw = type_list<>;
+};
+
+template<typename Entity, typename... Override>
+struct unpack_type<const basic_registry<Entity>, type_list<Override...>>
+    : unpack_type<basic_registry<Entity>, type_list<Override...>> {};
+
+template<typename Entity, typename... Component, typename... Exclude, typename... Override>
+struct unpack_type<basic_view<Entity, get_t<Component...>, exclude_t<Exclude...>>, type_list<Override...>> {
+    using ro = type_list_cat_t<type_list<Exclude...>, typename unpack_type<Component, type_list<Override...>>::ro...>;
+    using rw = type_list_cat_t<typename unpack_type<Component, type_list<Override...>>::rw...>;
+};
+
+template<typename Entity, typename... Component, typename... Exclude, typename... Override>
+struct unpack_type<const basic_view<Entity, get_t<Component...>, exclude_t<Exclude...>>, type_list<Override...>>
+    : unpack_type<basic_view<Entity, get_t<Component...>, exclude_t<Exclude...>>, type_list<Override...>> {};
+
+template<typename, typename>
+struct resource;
+
+template<typename... Args, typename... Req>
+struct resource<type_list<Args...>, type_list<Req...>> {
+    using args = type_list<std::remove_const_t<Args>...>;
+    using ro = type_list_cat_t<typename unpack_type<Args, type_list<Req...>>::ro..., typename unpack_type<Req, type_list<>>::ro...>;
+    using rw = type_list_cat_t<typename unpack_type<Args, type_list<Req...>>::rw..., typename unpack_type<Req, type_list<>>::rw...>;
+};
+
+template<typename... Req, typename Ret, typename... Args>
+resource<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> free_function_to_resource(Ret (*)(Args...));
+
+template<typename... Req, typename Ret, typename Type, typename... Args>
+resource<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource(Ret (*)(Type &, Args...));
+
+template<typename... Req, typename Ret, typename Class, typename... Args>
+resource<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource(Ret (Class::*)(Args...));
+
+template<typename... Req, typename Ret, typename Class, typename... Args>
+resource<type_list<std::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource(Ret (Class::*)(Args...) const);
+
+template<typename... Req>
+resource<type_list<>, type_list<Req...>> to_resource();
+
+} // 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.<br/>
+ * Note that the resulting tasks aren't executed in any case. This isn't the
+ * goal of the tool. Instead, they are returned to the user in the form of a
+ * graph that allows for safe execution.
+ *
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ */
+template<typename Entity>
+class basic_organizer final {
+    using callback_type = void(const void *, basic_registry<Entity> &);
+    using prepare_type = void(basic_registry<Entity> &);
+    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<typename Type>
+    [[nodiscard]] static decltype(auto) extract(basic_registry<Entity> &reg) {
+        if constexpr(std::is_same_v<Type, basic_registry<Entity>>) {
+            return reg;
+        } else if constexpr(internal::is_view_v<Type>) {
+            return as_view{reg};
+        } else {
+            return reg.template ctx_or_set<std::remove_reference_t<Type>>();
+        }
+    }
+
+    template<typename... Args>
+    [[nodiscard]] static auto to_args(basic_registry<Entity> &reg, type_list<Args...>) {
+        return std::tuple<decltype(extract<Args>(reg))...>(extract<Args>(reg)...);
+    }
+
+    template<typename... Type>
+    static std::size_t fill_dependencies(type_list<Type...>, [[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<Type>()...};
+            const auto length = (std::min)(count, sizeof...(Type));
+            std::copy_n(info, length, buffer);
+            return length;
+        }
+    }
+
+    template<typename... RO, typename... RW>
+    void track_dependencies(std::size_t index, const bool requires_registry, type_list<RO...>, type_list<RW...>) {
+        dependencies[type_hash<basic_registry<Entity>>::value()].emplace_back(index, requires_registry || (sizeof...(RO) + sizeof...(RW) == 0u));
+        (dependencies[type_hash<RO>::value()].emplace_back(index, false), ...);
+        (dependencies[type_hash<RW>::value()].emplace_back(index, true), ...);
+    }
+
+    [[nodiscard]] std::vector<bool> adjacency_matrix() {
+        const auto length = vertices.size();
+        std::vector<bool> edges(length * length, false);
+
+        // creates the ajacency 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<std::size_t> 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<std::size_t> &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<entity_type> &reg) const {
+            node.prepare ? node.prepare(reg) : void();
+        }
+
+    private:
+        bool is_top_level;
+        vertex_data node;
+        std::vector<std::size_t> 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<auto Candidate, typename... Req>
+    void emplace(const char *name = nullptr) {
+        using resource_type = decltype(internal::free_function_to_resource<Req...>(Candidate));
+        constexpr auto requires_registry = type_list_contains_v<typename resource_type::args, basic_registry<entity_type>>;
+
+        callback_type *callback = +[](const void *, basic_registry<entity_type> &reg) {
+            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<entity_type> &reg) { void(to_args(reg, typename resource_type::args{})); },
+            &type_id<std::integral_constant<decltype(Candidate), Candidate>>()};
+
+        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<auto Candidate, typename... Req, typename Type>
+    void emplace(Type &value_or_instance, const char *name = nullptr) {
+        using resource_type = decltype(internal::constrained_function_to_resource<Req...>(Candidate));
+        constexpr auto requires_registry = type_list_contains_v<typename resource_type::args, basic_registry<entity_type>>;
+
+        callback_type *callback = +[](const void *payload, basic_registry<entity_type> &reg) {
+            Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(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<entity_type> &reg) { void(to_args(reg, typename resource_type::args{})); },
+            &type_id<std::integral_constant<decltype(Candidate), Candidate>>()};
+
+        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<typename... Req>
+    void emplace(function_type *func, const void *payload = nullptr, const char *name = nullptr) {
+        using resource_type = internal::resource<type_list<>, type_list<Req...>>;
+        track_dependencies(vertices.size(), true, typename resource_type::ro{}, typename resource_type::rw{});
+
+        vertex_data vdata{
+            resource_type::ro::size,
+            resource_type::rw::size,
+            name,
+            payload,
+            func,
+            +[](const bool rw, const type_info **buffer, const 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<void>()};
+
+        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<vertex> graph() {
+        const auto edges = adjacency_matrix();
+
+        // creates the adjacency list
+        std::vector<vertex> adjacency_list{};
+        adjacency_list.reserve(vertices.size());
+
+        for(std::size_t col{}, length = vertices.size(); col < length; ++col) {
+            std::vector<std::size_t> 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_hash_map<id_type, std::vector<std::pair<std::size_t, bool>>, identity> dependencies;
+    std::vector<vertex_data> vertices;
+};
+
+} // namespace entt
+
+#endif

+ 1514 - 0
Dependencies/include/entt/entity/registry.hpp

@@ -0,0 +1,1514 @@
+#ifndef ENTT_ENTITY_REGISTRY_HPP
+#define ENTT_ENTITY_REGISTRY_HPP
+
+#include <algorithm>
+#include <cstddef>
+#include <iterator>
+#include <memory>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <vector>
+#include "../config/config.h"
+#include "../container/dense_hash_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<typename It>
+class storage_proxy_iterator {
+    template<typename Other>
+    friend class storage_proxy_iterator;
+
+    using iterator_traits = std::iterator_traits<It>;
+    using first_type = typename iterator_traits::value_type::first_type;
+    using second_type = typename iterator_traits::value_type::second_type::element_type;
+
+public:
+    using value_type = std::pair<first_type, constness_as_t<second_type, std::remove_reference_t<typename iterator_traits::reference>> &>;
+    using pointer = input_iterator_pointer<value_type>;
+    using reference = value_type;
+    using difference_type = typename iterator_traits::difference_type;
+    using iterator_category = std::input_iterator_tag;
+
+    storage_proxy_iterator() ENTT_NOEXCEPT = default;
+
+    storage_proxy_iterator(const It iter) ENTT_NOEXCEPT
+        : it{iter} {}
+
+    template<typename Other, typename = std::enable_if_t<!std::is_same_v<It, Other> && std::is_constructible_v<It, Other>>>
+    storage_proxy_iterator(const storage_proxy_iterator<Other> &other)
+        : 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 {
+        return {it[value].first, *it[value].second};
+    }
+
+    [[nodiscard]] reference operator*() const {
+        return {it->first, *it->second};
+    }
+
+    [[nodiscard]] pointer operator->() const {
+        return operator*();
+    }
+
+    template<typename ILhs, typename IRhs>
+    friend auto operator-(const storage_proxy_iterator<ILhs> &, const storage_proxy_iterator<IRhs> &) ENTT_NOEXCEPT;
+
+    template<typename ILhs, typename IRhs>
+    friend bool operator==(const storage_proxy_iterator<ILhs> &, const storage_proxy_iterator<IRhs> &) ENTT_NOEXCEPT;
+
+    template<typename ILhs, typename IRhs>
+    friend bool operator<(const storage_proxy_iterator<ILhs> &, const storage_proxy_iterator<IRhs> &) ENTT_NOEXCEPT;
+
+private:
+    It it;
+};
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] auto operator-(const storage_proxy_iterator<ILhs> &lhs, const storage_proxy_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return lhs.it - rhs.it;
+}
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator==(const storage_proxy_iterator<ILhs> &lhs, const storage_proxy_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return lhs.it == rhs.it;
+}
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator!=(const storage_proxy_iterator<ILhs> &lhs, const storage_proxy_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return !(lhs == rhs);
+}
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator<(const storage_proxy_iterator<ILhs> &lhs, const storage_proxy_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return lhs.it < rhs.it;
+}
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator>(const storage_proxy_iterator<ILhs> &lhs, const storage_proxy_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return rhs < lhs;
+}
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator<=(const storage_proxy_iterator<ILhs> &lhs, const storage_proxy_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return !(lhs > rhs);
+}
+
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator>=(const storage_proxy_iterator<ILhs> &lhs, const storage_proxy_iterator<IRhs> &rhs) ENTT_NOEXCEPT {
+    return !(lhs < rhs);
+}
+
+} // 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<typename Entity>
+class basic_registry {
+    using entity_traits = entt_traits<Entity>;
+    using basic_common_type = basic_sparse_set<Entity>;
+
+    template<typename Component>
+    using storage_type = typename storage_traits<Entity, Component>::storage_type;
+
+    template<typename...>
+    struct group_handler;
+
+    template<typename... Exclude, typename... Get, typename... Owned>
+    struct group_handler<exclude_t<Exclude...>, get_t<Get...>, Owned...> {
+        // nasty workaround for an issue with the toolset v141 that doesn't accept a fold expression here
+        static_assert(!std::disjunction_v<std::bool_constant<component_traits<Owned>::in_place_delete>...>, "Groups do not support in-place delete");
+        std::conditional_t<sizeof...(Owned) == 0, basic_common_type, std::size_t> current{};
+
+        template<typename Component>
+        void maybe_valid_if(basic_registry &owner, const Entity entt) {
+            [[maybe_unused]] const auto cpools = std::forward_as_tuple(owner.assure<Owned>()...);
+
+            const auto is_valid = ((std::is_same_v<Component, Owned> || std::get<storage_type<Owned> &>(cpools).contains(entt)) && ...)
+                                  && ((std::is_same_v<Component, Get> || owner.assure<Get>().contains(entt)) && ...)
+                                  && ((std::is_same_v<Component, Exclude> || !owner.assure<Exclude>().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<storage_type<Owned> &>(cpools).swap_elements(std::get<storage_type<Owned> &>(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<Owned>()...); std::get<0>(cpools).contains(entt) && (std::get<0>(cpools).index(entt) < current)) {
+                    const auto pos = --current;
+                    (std::get<storage_type<Owned> &>(cpools).swap_elements(std::get<storage_type<Owned> &>(cpools).data()[pos], entt), ...);
+                }
+            }
+        }
+    };
+
+    struct group_data {
+        std::size_t size;
+        std::unique_ptr<void, void (*)(void *)> group;
+        bool (*owned)(const id_type) ENTT_NOEXCEPT;
+        bool (*get)(const id_type) ENTT_NOEXCEPT;
+        bool (*exclude)(const id_type) ENTT_NOEXCEPT;
+    };
+
+    template<typename Component>
+    [[nodiscard]] auto &assure(const id_type id = type_hash<Component>::value()) {
+        static_assert(std::is_same_v<Component, std::decay_t<Component>>, "Non-decayed types not allowed");
+        auto &&cpool = pools[id];
+
+        if(!cpool) {
+            cpool.reset(new storage_type<Component>{});
+            cpool->bind(forward_as_any(*this));
+        }
+
+        return static_cast<storage_type<Component> &>(*cpool);
+    }
+
+    template<typename Component>
+    [[nodiscard]] const auto &assure(const id_type id = type_hash<Component>::value()) const {
+        static_assert(std::is_same_v<Component, std::decay_t<Component>>, "Non-decayed types not allowed");
+
+        if(const auto it = pools.find(id); it != pools.cend()) {
+            return static_cast<const storage_type<Component> &>(*it->second);
+        }
+
+        static storage_type<Component> placeholder{};
+        return placeholder;
+    }
+
+    auto generate_identifier(const std::size_t pos) ENTT_NOEXCEPT {
+        ENTT_ASSERT(pos < entity_traits::to_integral(null), "No entities available");
+        return entity_traits::combine(static_cast<typename entity_traits::entity_type>(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 Default constructor. */
+    basic_registry() = default;
+
+    /**
+     * @brief Move constructor.
+     * @param other The instance to move from.
+     */
+    basic_registry(basic_registry &&other) ENTT_NOEXCEPT
+        : pools{std::move(other.pools)},
+          vars{std::move(other.vars)},
+          groups{std::move(other.groups)},
+          entities{std::move(other.entities)},
+          free_list{other.free_list} {
+        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) ENTT_NOEXCEPT {
+        pools = std::move(other.pools);
+        vars = std::move(other.vars);
+        groups = std::move(other.groups);
+        entities = std::move(other.entities);
+        free_list = other.free_list;
+
+        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 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<typename Component>
+    [[nodiscard]] decltype(auto) storage(const id_type id = type_hash<Component>::value()) {
+        return assure<Component>(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<typename Component>
+    [[nodiscard]] decltype(auto) storage(const id_type id = type_hash<Component>::value()) const {
+        return assure<Component>(id);
+    }
+
+    /**
+     * @brief Returns the storage associated with a given name, if any.
+     * @param id Name used to map the storage within the registry.
+     * @return The requested storage if it exists, a null pointer otherwise.
+     */
+    [[nodiscard]] base_type *storage(const id_type id) {
+        return const_cast<base_type *>(std::as_const(*this).storage(id));
+    }
+
+    /**
+     * @brief Returns the storage associated with a given name, if any.
+     * @param id Name used to map the storage within the registry.
+     * @return The requested storage if it exists, a null pointer otherwise.
+     */
+    [[nodiscard]] const base_type *storage(const id_type id) const {
+        const auto it = pools.find(id);
+        return it == pools.end() ? nullptr : it->second.get();
+    }
+
+    /**
+     * @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`.<br/>
+     * 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<typename It>
+    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`.<br/>
+     * 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<typename It>
+    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, 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<typename It>
+    void release(It first, It last) {
+        for(; first != last; ++first) {
+            release(*first, entity_traits::to_version(*first) + 1u);
+        }
+    }
+
+    /**
+     * @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, 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(auto &&curr: pools) {
+            curr.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<typename It>
+    void destroy(It first, It last) {
+        if constexpr(is_iterator_type_v<typename base_type::iterator, It>) {
+            for(; first != last; ++first) {
+                destroy(*first, entity_traits::to_version(*first) + 1u);
+            }
+        } else {
+            for(auto &&curr: pools) {
+                curr.second->remove(first, last);
+            }
+
+            release(first, last);
+        }
+    }
+
+    /**
+     * @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<typename Component, typename... Args>
+    decltype(auto) emplace(const entity_type entity, Args &&...args) {
+        ENTT_ASSERT(valid(entity), "Invalid entity");
+        return assure<Component>().emplace(entity, std::forward<Args>(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<typename Component, typename It>
+    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<Component>().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<typename Component, typename EIt, typename CIt, typename = std::enable_if_t<std::is_same_v<std::decay_t<typename std::iterator_traits<CIt>::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<Component>().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<typename Component, typename... Args>
+    decltype(auto) emplace_or_replace(const entity_type entity, Args &&...args) {
+        ENTT_ASSERT(valid(entity), "Invalid entity");
+        auto &cpool = assure<Component>();
+
+        return cpool.contains(entity)
+                   ? cpool.patch(entity, [&args...](auto &...curr) { ((curr = Component{std::forward<Args>(args)...}), ...); })
+                   : cpool.emplace(entity, std::forward<Args>(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<typename Component, typename... Func>
+    decltype(auto) patch(const entity_type entity, Func &&...func) {
+        ENTT_ASSERT(valid(entity), "Invalid entity");
+        return assure<Component>().patch(entity, std::forward<Func>(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<typename Component, typename... Args>
+    decltype(auto) replace(const entity_type entity, Args &&...args) {
+        ENTT_ASSERT(valid(entity), "Invalid entity");
+        return assure<Component>().patch(entity, [&args...](auto &...curr) { ((curr = Component{std::forward<Args>(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<typename Component, typename... Other>
+    size_type remove(const entity_type entity) {
+        ENTT_ASSERT(valid(entity), "Invalid entity");
+        return (assure<Component>().remove(entity) + ... + assure<Other>().remove(entity));
+    }
+
+    /**
+     * @brief Removes the given components from all the entities in a range.
+     *
+     * @sa remove
+     *
+     * @tparam Component Types of components 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<typename Component, typename... Other, typename It>
+    size_type remove(It first, It last) {
+        size_type count{};
+
+        for(const auto cpools = std::forward_as_tuple(assure<Component>(), assure<Other>()...); first != last; ++first) {
+            const auto entity = *first;
+            ENTT_ASSERT(valid(entity), "Invalid entity");
+            count += (std::get<storage_type<Component> &>(cpools).remove(entity) + ... + std::get<storage_type<Other> &>(cpools).remove(entity));
+        }
+
+        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<typename Component, typename... Other>
+    void erase(const entity_type entity) {
+        ENTT_ASSERT(valid(entity), "Invalid entity");
+        (assure<Component>().erase(entity), (assure<Other>().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<typename Component, typename... Other, typename It>
+    void erase(It first, It last) {
+        for(const auto cpools = std::forward_as_tuple(assure<Component>(), assure<Other>()...); first != last; ++first) {
+            const auto entity = *first;
+            ENTT_ASSERT(valid(entity), "Invalid entity");
+            (std::get<storage_type<Component> &>(cpools).erase(entity), (std::get<storage_type<Other> &>(cpools).erase(entity), ...));
+        }
+    }
+
+    /**
+     * @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<typename... Component>
+    void compact() {
+        if constexpr(sizeof...(Component) == 0) {
+            for(auto &&curr: pools) {
+                curr.second->compact();
+            }
+        } else {
+            (assure<Component>().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<typename... Component>
+    [[nodiscard]] bool all_of(const entity_type entity) const {
+        ENTT_ASSERT(valid(entity), "Invalid entity");
+        return (assure<std::remove_const_t<Component>>().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<typename... Component>
+    [[nodiscard]] bool any_of(const entity_type entity) const {
+        ENTT_ASSERT(valid(entity), "Invalid entity");
+        return (assure<std::remove_const_t<Component>>().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<typename... Component>
+    [[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entity) const {
+        ENTT_ASSERT(valid(entity), "Invalid entity");
+        return view<Component...>().template get<const Component...>(entity);
+    }
+
+    /*! @copydoc get */
+    template<typename... Component>
+    [[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entity) {
+        ENTT_ASSERT(valid(entity), "Invalid entity");
+        return view<Component...>().template get<Component...>(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<typename Component, typename... Args>
+    [[nodiscard]] decltype(auto) get_or_emplace(const entity_type entity, Args &&...args) {
+        ENTT_ASSERT(valid(entity), "Invalid entity");
+        auto &cpool = assure<Component>();
+        return cpool.contains(entity) ? cpool.get(entity) : cpool.emplace(entity, std::forward<Args>(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<typename... Component>
+    [[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<std::remove_const_t<Component>...>();
+            return cpool.contains(entity) ? std::addressof(cpool.get(entity)) : nullptr;
+        } else {
+            return std::make_tuple(try_get<Component>(entity)...);
+        }
+    }
+
+    /*! @copydoc try_get */
+    template<typename... Component>
+    [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entity) {
+        if constexpr(sizeof...(Component) == 1) {
+            return (const_cast<Component *>(std::as_const(*this).template try_get<Component>(entity)), ...);
+        } else {
+            return std::make_tuple(try_get<Component>(entity)...);
+        }
+    }
+
+    /**
+     * @brief Clears a whole registry or the pools for the given components.
+     * @tparam Component Types of components to remove from their entities.
+     */
+    template<typename... Component>
+    void clear() {
+        if constexpr(sizeof...(Component) == 0) {
+            for(auto &&curr: pools) {
+                curr.second->clear();
+            }
+
+            each([this](const auto entity) { release_entity(entity, entity_traits::to_version(entity) + 1u); });
+        } else {
+            (assure<Component>().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<typename Func>
+    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.<br/>
+     * The function type for a listener is equivalent to:
+     *
+     * @code{.cpp}
+     * void(basic_registry<Entity> &, 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<typename Component>
+    [[nodiscard]] auto on_construct() {
+        return assure<Component>().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.<br/>
+     * The function type for a listener is equivalent to:
+     *
+     * @code{.cpp}
+     * void(basic_registry<Entity> &, 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<typename Component>
+    [[nodiscard]] auto on_update() {
+        return assure<Component>().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.<br/>
+     * The function type for a listener is equivalent to:
+     *
+     * @code{.cpp}
+     * void(basic_registry<Entity> &, 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<typename Component>
+    [[nodiscard]] auto on_destroy() {
+        return assure<Component>().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.<br/>
+     * 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<typename Component, typename... Other, typename... Exclude>
+    [[nodiscard]] basic_view<entity_type, get_t<std::add_const_t<Component>, std::add_const_t<Other>...>, exclude_t<Exclude...>> view(exclude_t<Exclude...> = {}) const {
+        return {assure<std::remove_const_t<Component>>(), assure<std::remove_const_t<Other>>()..., assure<Exclude>()...};
+    }
+
+    /*! @copydoc view */
+    template<typename Component, typename... Other, typename... Exclude>
+    [[nodiscard]] basic_view<entity_type, get_t<Component, Other...>, exclude_t<Exclude...>> view(exclude_t<Exclude...> = {}) {
+        return {assure<std::remove_const_t<Component>>(), assure<std::remove_const_t<Other>>()..., assure<Exclude>()...};
+    }
+
+    /**
+     * @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.<br/>
+     * 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.<br/>
+     * 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<typename... Owned, typename... Get, typename... Exclude>
+    [[nodiscard]] basic_group<entity_type, owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>> group(get_t<Get...>, exclude_t<Exclude...> = {}) {
+        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<exclude_t<std::remove_const_t<Exclude>...>, get_t<std::remove_const_t<Get>...>, std::remove_const_t<Owned>...>;
+
+        const auto cpools = std::forward_as_tuple(assure<std::remove_const_t<Owned>>()..., assure<std::remove_const_t<Get>>()...);
+        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<std::remove_const_t<Owned>>::value()) && ...)
+                   && (gdata.get(type_hash<std::remove_const_t<Get>>::value()) && ...)
+                   && (gdata.exclude(type_hash<Exclude>::value()) && ...);
+        });
+
+        if(it != groups.cend()) {
+            handler = static_cast<handler_type *>(it->group.get());
+        } else {
+            group_data candidate = {
+                size,
+                {new handler_type{}, [](void *instance) { delete static_cast<handler_type *>(instance); }},
+                []([[maybe_unused]] const id_type ctype) ENTT_NOEXCEPT { return ((ctype == type_hash<std::remove_const_t<Owned>>::value()) || ...); },
+                []([[maybe_unused]] const id_type ctype) ENTT_NOEXCEPT { return ((ctype == type_hash<std::remove_const_t<Get>>::value()) || ...); },
+                []([[maybe_unused]] const id_type ctype) ENTT_NOEXCEPT { return ((ctype == type_hash<Exclude>::value()) || ...); },
+            };
+
+            handler = static_cast<handler_type *>(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<std::remove_const_t<Owned>>::value()));
+                    const auto sz = overlapping + (0u + ... + gdata.get(type_hash<std::remove_const_t<Get>>::value())) + (0u + ... + gdata.exclude(type_hash<Exclude>::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<std::remove_const_t<Owned>>::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<std::remove_const_t<Owned>>::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<std::remove_const_t<Owned>>().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if<std::remove_const_t<Owned>>>(*handler), ...);
+            (on_construct<std::remove_const_t<Get>>().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if<std::remove_const_t<Get>>>(*handler), ...);
+            (on_destroy<Exclude>().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if<Exclude>>(*handler), ...);
+
+            (on_destroy<std::remove_const_t<Owned>>().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...);
+            (on_destroy<std::remove_const_t<Get>>().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...);
+            (on_construct<Exclude>().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...);
+
+            if constexpr(sizeof...(Owned) == 0) {
+                for(const auto entity: view<Owned..., Get...>(exclude<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<type_list_element_t<0, type_list<std::remove_const_t<Owned>...>>>(*this, *first);
+                }
+            }
+        }
+
+        return {handler->current, std::get<storage_type<std::remove_const_t<Owned>> &>(cpools)..., std::get<storage_type<std::remove_const_t<Get>> &>(cpools)...};
+    }
+
+    /*! @copydoc group */
+    template<typename... Owned, typename... Get, typename... Exclude>
+    [[nodiscard]] basic_group<entity_type, owned_t<std::add_const_t<Owned>...>, get_t<std::add_const_t<Get>...>, exclude_t<Exclude...>> group_if_exists(get_t<Get...>, exclude_t<Exclude...> = {}) 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<std::remove_const_t<Owned>>::value()) && ...)
+                   && (gdata.get(type_hash<std::remove_const_t<Get>>::value()) && ...)
+                   && (gdata.exclude(type_hash<Exclude>::value()) && ...);
+        });
+
+        if(it == groups.cend()) {
+            return {};
+        } else {
+            using handler_type = group_handler<exclude_t<std::remove_const_t<Exclude>...>, get_t<std::remove_const_t<Get>...>, std::remove_const_t<Owned>...>;
+            return {static_cast<handler_type *>(it->group.get())->current, assure<std::remove_const_t<Owned>>()..., assure<std::remove_const_t<Get>>()...};
+        }
+    }
+
+    /*! @copydoc group */
+    template<typename... Owned, typename... Exclude>
+    [[nodiscard]] basic_group<entity_type, owned_t<Owned...>, get_t<>, exclude_t<Exclude...>> group(exclude_t<Exclude...> = {}) {
+        return group<Owned...>(get_t<>{}, exclude<Exclude...>);
+    }
+
+    /*! @copydoc group */
+    template<typename... Owned, typename... Exclude>
+    [[nodiscard]] basic_group<entity_type, owned_t<std::add_const_t<Owned>...>, get_t<>, exclude_t<Exclude...>> group_if_exists(exclude_t<Exclude...> = {}) const {
+        return group_if_exists<std::add_const_t<Owned>...>(get_t<>{}, exclude<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 sortable, false
+     * otherwise.
+     */
+    template<typename... Component>
+    [[nodiscard]] bool sortable() const {
+        return std::none_of(groups.cbegin(), groups.cend(), [](auto &&gdata) { return (gdata.owned(type_hash<std::remove_const_t<Component>>::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<typename... Owned, typename... Get, typename... Exclude>
+    [[nodiscard]] bool sortable(const basic_group<entity_type, owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>> &) ENTT_NOEXCEPT {
+        constexpr auto size = sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude);
+        auto pred = [size](const auto &gdata) { return (0u + ... + gdata.owned(type_hash<std::remove_const_t<Owned>>::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.<br/>
+     * 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.<br/>
+     * The sort function object offers an `operator()` that accepts:
+     *
+     * * An iterator to the first element of the range to sort.
+     * * An iterator past the last element of the range to sort.
+     * * A comparison function object to use to compare the elements.
+     *
+     * The comparison function object hasn't necessarily the type of the one
+     * passed along with the other parameters to this member function.
+     *
+     * @warning
+     * Pools of 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<typename Component, typename Compare, typename Sort = std_sort, typename... Args>
+    void sort(Compare compare, Sort algo = Sort{}, Args &&...args) {
+        ENTT_ASSERT(sortable<Component>(), "Cannot sort owned storage");
+        auto &cpool = assure<Component>();
+
+        if constexpr(std::is_invocable_v<Compare, decltype(cpool.get({})), decltype(cpool.get({}))>) {
+            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>(args)...);
+        } else {
+            cpool.sort(std::move(compare), std::move(algo), std::forward<Args>(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<typename To, typename From>
+    void sort() {
+        ENTT_ASSERT(sortable<To>(), "Cannot sort owned storage");
+        assure<To>().respect(assure<From>());
+    }
+
+    /**
+     * @brief Binds an object to the context of the registry.
+     *
+     * If the value already exists it is overwritten, otherwise a new instance
+     * of the given type is created and initialized with the arguments provided.
+     *
+     * @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.
+     */
+    template<typename Type, typename... Args>
+    Type &set(Args &&...args) {
+        auto &&elem = vars[type_hash<std::remove_const_t<std::remove_reference_t<Type>>>::value()];
+        elem.template emplace<Type>(std::forward<Args>(args)...);
+        return any_cast<Type &>(elem);
+    }
+
+    /**
+     * @brief Unsets a context variable if it exists.
+     * @tparam Type Type of object to set.
+     */
+    template<typename Type>
+    void unset() {
+        vars.erase(type_hash<std::remove_const_t<std::remove_reference_t<Type>>>::value());
+    }
+
+    /**
+     * @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 A reference to the object in the context of the registry.
+     */
+    template<typename Type, typename... Args>
+    [[nodiscard]] Type &ctx_or_set(Args &&...args) {
+        return any_cast<Type &>(vars.try_emplace(type_hash<std::remove_const_t<std::remove_reference_t<Type>>>::value(), std::in_place_type<Type>, std::forward<Args>(args)...).first->second);
+    }
+
+    /**
+     * @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.
+     */
+    template<typename Type>
+    [[nodiscard]] std::add_const_t<Type> *try_ctx() const {
+        auto it = vars.find(type_hash<std::remove_const_t<std::remove_reference_t<Type>>>::value());
+        return it == vars.cend() ? nullptr : any_cast<std::add_const_t<Type>>(&it->second);
+    }
+
+    /*! @copydoc try_ctx */
+    template<typename Type>
+    [[nodiscard]] Type *try_ctx() {
+        auto it = vars.find(type_hash<std::remove_const_t<std::remove_reference_t<Type>>>::value());
+        return it == vars.end() ? nullptr : any_cast<Type>(&it->second);
+    }
+
+    /**
+     * @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.
+     *
+     * @tparam Type Type of object to get.
+     * @return A valid reference to the object in the context of the registry.
+     */
+    template<typename Type>
+    [[nodiscard]] std::add_const_t<Type> &ctx() const {
+        return any_cast<std::add_const_t<Type> &>(vars.at(type_hash<std::remove_const_t<std::remove_reference_t<Type>>>::value()));
+    }
+
+    /*! @copydoc ctx */
+    template<typename Type>
+    [[nodiscard]] Type &ctx() {
+        return any_cast<Type &>(vars.at(type_hash<std::remove_const_t<std::remove_reference_t<Type>>>::value()));
+    }
+
+    /**
+     * @brief Visits a registry and returns the type info for its context
+     * variables.
+     *
+     * The signature of the function should be equivalent to the following:
+     *
+     * @code{.cpp}
+     * void(const type_info &);
+     * @endcode
+     *
+     * Returned identifiers are those of the context variables currently set.
+     *
+     * @sa type_info
+     *
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename Func>
+    void ctx(Func func) const {
+        for(auto &&curr: vars) {
+            func(curr.second.type());
+        }
+    }
+
+private:
+    dense_hash_map<id_type, std::unique_ptr<base_type>, identity> pools{};
+    dense_hash_map<id_type, basic_any<0u>, identity> vars{};
+    std::vector<group_data> groups{};
+    std::vector<entity_type> entities{};
+    entity_type free_list{tombstone};
+};
+
+} // namespace entt
+
+#endif

+ 267 - 0
Dependencies/include/entt/entity/runtime_view.hpp

@@ -0,0 +1,267 @@
+#ifndef ENTT_ENTITY_RUNTIME_VIEW_HPP
+#define ENTT_ENTITY_RUNTIME_VIEW_HPP
+
+#include <algorithm>
+#include <iterator>
+#include <type_traits>
+#include <utility>
+#include <vector>
+#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<typename Type>
+class runtime_view_iterator final {
+    [[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 iterator_type = typename Type::iterator;
+    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 = default;
+
+    runtime_view_iterator(const std::vector<const Type *> &cpools, const std::vector<const Type *> &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--() ENTT_NOEXCEPT {
+        while(--it != (*pools)[0]->begin() && !valid()) {}
+        return *this;
+    }
+
+    runtime_view_iterator operator--(int) ENTT_NOEXCEPT {
+        runtime_view_iterator orig = *this;
+        return operator--(), orig;
+    }
+
+    [[nodiscard]] pointer operator->() const {
+        return it.operator->();
+    }
+
+    [[nodiscard]] reference operator*() const {
+        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<const Type *> *pools;
+    const std::vector<const Type *> *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<typename>
+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.<br/>
+ * 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<typename Entity, typename Allocator>
+struct basic_runtime_view<basic_sparse_set<Entity, Allocator>> final {
+    /*! @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<Entity, Allocator>;
+    /*! @brief Bidirectional iterator type. */
+    using iterator = internal::runtime_view_iterator<base_type>;
+
+    /*! @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.<br/>
+     * The signature of the function should be equivalent to the following:
+     *
+     * @code{.cpp}
+     * void(const entity_type);
+     * @endcode
+     *
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename Func>
+    void each(Func func) const {
+        for(const auto entity: *this) {
+            func(entity);
+        }
+    }
+
+private:
+    std::vector<const base_type *> pools;
+    std::vector<const base_type *> filter;
+};
+
+} // namespace entt
+
+#endif

+ 562 - 0
Dependencies/include/entt/entity/snapshot.hpp

@@ -0,0 +1,562 @@
+#ifndef ENTT_ENTITY_SNAPSHOT_HPP
+#define ENTT_ENTITY_SNAPSHOT_HPP
+
+#include <array>
+#include <cstddef>
+#include <iterator>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <vector>
+#include "../config/config.h"
+#include "../container/dense_hash_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.<br/>
+ * 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<typename Entity>
+class basic_snapshot {
+    using entity_traits = entt_traits<Entity>;
+
+    template<typename Component, typename Archive, typename It>
+    void get(Archive &archive, std::size_t sz, It first, It last) const {
+        const auto view = reg->template view<std::add_const_t<Component>>();
+        archive(typename entity_traits::entity_type(sz));
+
+        while(first != last) {
+            const auto entt = *(first++);
+
+            if(reg->template all_of<Component>(entt)) {
+                std::apply(archive, std::tuple_cat(std::make_tuple(entt), view.get(entt)));
+            }
+        }
+    }
+
+    template<typename... Component, typename Archive, typename It, std::size_t... Index>
+    void component(Archive &archive, It first, It last, std::index_sequence<Index...>) const {
+        std::array<std::size_t, sizeof...(Index)> size{};
+        auto begin = first;
+
+        while(begin != last) {
+            const auto entt = *(begin++);
+            ((reg->template all_of<Component>(entt) ? ++size[Index] : 0u), ...);
+        }
+
+        (get<Component>(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<entity_type> &source) ENTT_NOEXCEPT
+        : reg{&source} {}
+
+    /*! @brief Default move constructor. */
+    basic_snapshot(basic_snapshot &&) = default;
+
+    /*! @brief Default move assignment operator. @return This snapshot. */
+    basic_snapshot &operator=(basic_snapshot &&) = 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<typename Archive>
+    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<typename... Component, typename Archive>
+    const basic_snapshot &component(Archive &archive) const {
+        if constexpr(sizeof...(Component) == 1u) {
+            const auto view = reg->template view<const Component...>();
+            (component<Component>(archive, view.rbegin(), view.rend()), ...);
+            return *this;
+        } else {
+            (component<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<typename... Component, typename Archive, typename It>
+    const basic_snapshot &component(Archive &archive, It first, It last) const {
+        component<Component...>(archive, first, last, std::index_sequence_for<Component...>{});
+        return *this;
+    }
+
+private:
+    const basic_registry<entity_type> *reg;
+};
+
+/**
+ * @brief Utility class to restore a snapshot as a whole.
+ *
+ * A snapshot loader requires that the destination registry be empty and loads
+ * all the data at once while keeping intact the identifiers that the entities
+ * originally had.<br/>
+ * An example of use is the implementation of a save/restore utility.
+ *
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ */
+template<typename Entity>
+class basic_snapshot_loader {
+    using entity_traits = entt_traits<Entity>;
+
+    template<typename Type, typename Archive>
+    void assign(Archive &archive) const {
+        typename entity_traits::entity_type length{};
+        archive(length);
+
+        entity_type entt{};
+
+        if constexpr(ignore_as_empty_v<std::remove_const_t<Type>>) {
+            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<Type>(entity);
+            }
+        } 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<Type>(entity, 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<entity_type> &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;
+
+    /*! @brief Default move assignment operator. @return This loader. */
+    basic_snapshot_loader &operator=(basic_snapshot_loader &&) = 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<typename Archive>
+    const basic_snapshot_loader &entities(Archive &archive) const {
+        typename entity_traits::entity_type length{};
+
+        archive(length);
+        std::vector<entity_type> 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<typename... Component, typename Archive>
+    const basic_snapshot_loader &component(Archive &archive) const {
+        (assign<Component>(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.<br/>
+     * 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<entity_type> *reg;
+};
+
+/**
+ * @brief Utility class for _continuous loading_.
+ *
+ * A _continuous loader_ is designed to load data from a source registry to a
+ * (possibly) non-empty destination. The loader can accommodate in a registry
+ * more than one snapshot in a sort of _continuous loading_ that updates the
+ * destination one step at a time.<br/>
+ * Identifiers that entities originally had are not transferred to the target.
+ * Instead, the loader maps remote identifiers to local ones while restoring a
+ * snapshot.<br/>
+ * An example of use is the implementation of a client-server 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<typename Entity>
+class basic_continuous_loader {
+    using entity_traits = entt_traits<Entity>;
+
+    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<typename Container>
+    auto update(int, Container &container)
+        -> decltype(typename Container::mapped_type{}, void()) {
+        // map like container
+        Container other;
+
+        for(auto &&pair: container) {
+            using first_type = std::remove_const_t<typename std::decay_t<decltype(pair)>::first_type>;
+            using second_type = typename std::decay_t<decltype(pair)>::second_type;
+
+            if constexpr(std::is_same_v<first_type, entity_type> && std::is_same_v<second_type, entity_type>) {
+                other.emplace(map(pair.first), map(pair.second));
+            } else if constexpr(std::is_same_v<first_type, entity_type>) {
+                other.emplace(map(pair.first), std::move(pair.second));
+            } else {
+                static_assert(std::is_same_v<second_type, entity_type>, "Neither the key nor the value are of entity type");
+                other.emplace(std::move(pair.first), map(pair.second));
+            }
+        }
+
+        std::swap(container, other);
+    }
+
+    template<typename Container>
+    auto update(char, Container &container)
+        -> decltype(typename Container::value_type{}, void()) {
+        // vector like container
+        static_assert(std::is_same_v<typename Container::value_type, entity_type>, "Invalid value type");
+
+        for(auto &&entt: container) {
+            entt = map(entt);
+        }
+    }
+
+    template<typename Other, typename Type, typename Member>
+    void update([[maybe_unused]] Other &instance, [[maybe_unused]] Member Type::*member) {
+        if constexpr(!std::is_same_v<Other, Type>) {
+            return;
+        } else if constexpr(std::is_same_v<Member, entity_type>) {
+            instance.*member = map(instance.*member);
+        } else {
+            // maybe a container? let's try...
+            update(0, instance.*member);
+        }
+    }
+
+    template<typename Component>
+    void remove_if_exists() {
+        for(auto &&ref: remloc) {
+            const auto local = ref.second.first;
+
+            if(reg->valid(local)) {
+                reg->template remove<Component>(local);
+            }
+        }
+    }
+
+    template<typename Other, typename Archive, typename... Type, typename... Member>
+    void assign(Archive &archive, [[maybe_unused]] Member Type::*...member) {
+        typename entity_traits::entity_type length{};
+        archive(length);
+
+        entity_type entt{};
+
+        if constexpr(ignore_as_empty_v<std::remove_const_t<Other>>) {
+            while(length--) {
+                archive(entt);
+                restore(entt);
+                reg->template emplace_or_replace<Other>(map(entt));
+            }
+        } else {
+            Other instance{};
+
+            while(length--) {
+                archive(entt, instance);
+                (update(instance, member), ...);
+                restore(entt);
+                reg->template emplace_or_replace<Other>(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<entity_type> &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<typename Archive>
+    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.<br/>
+     * 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<typename... Component, typename Archive, typename... Type, typename... Member>
+    basic_continuous_loader &component(Archive &archive, Member Type::*...member) {
+        (remove_if_exists<Component>(), ...);
+        (assign<Component>(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.<br/>
+     * 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_hash_map<entity_type, std::pair<entity_type, bool>> remloc;
+    basic_registry<entity_type> *reg;
+};
+
+} // namespace entt
+
+#endif

+ 947 - 0
Dependencies/include/entt/entity/sparse_set.hpp

@@ -0,0 +1,947 @@
+#ifndef ENTT_ENTITY_SPARSE_SET_HPP
+#define ENTT_ENTITY_SPARSE_SET_HPP
+
+#include <cstddef>
+#include <iterator>
+#include <memory>
+#include <type_traits>
+#include <utility>
+#include <vector>
+#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<typename Container>
+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 = default;
+
+    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 {
+        const auto pos = offset - value - 1;
+        return packed->data()[pos];
+    }
+
+    [[nodiscard]] pointer operator->() const {
+        const auto pos = offset - 1;
+        return packed->data() + pos;
+    }
+
+    [[nodiscard]] reference operator*() const {
+        return *operator->();
+    }
+
+    [[nodiscard]] difference_type index() const ENTT_NOEXCEPT {
+        return offset;
+    }
+
+private:
+    const Container *packed;
+    difference_type offset;
+};
+
+template<typename Type, typename Other>
+[[nodiscard]] auto operator-(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) ENTT_NOEXCEPT {
+    return rhs.index() - lhs.index();
+}
+
+template<typename Type, typename Other>
+[[nodiscard]] bool operator==(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) ENTT_NOEXCEPT {
+    return lhs.index() == rhs.index();
+}
+
+template<typename Type, typename Other>
+[[nodiscard]] bool operator!=(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) ENTT_NOEXCEPT {
+    return !(lhs == rhs);
+}
+
+template<typename Type, typename Other>
+[[nodiscard]] bool operator<(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) ENTT_NOEXCEPT {
+    return lhs.index() > rhs.index();
+}
+
+template<typename Type, typename Other>
+[[nodiscard]] bool operator>(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) ENTT_NOEXCEPT {
+    return lhs.index() < rhs.index();
+}
+
+template<typename Type, typename Other>
+[[nodiscard]] bool operator<=(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &rhs) ENTT_NOEXCEPT {
+    return !(lhs > rhs);
+}
+
+template<typename Type, typename Other>
+[[nodiscard]] bool operator>=(const sparse_set_iterator<Type> &lhs, const sparse_set_iterator<Other> &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.<br/>
+ * 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.<br/>
+ * 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<typename Entity, typename Allocator>
+class basic_sparse_set {
+    using allocator_traits = std::allocator_traits<Allocator>;
+    using alloc = typename allocator_traits::template rebind_alloc<Entity>;
+    using alloc_traits = typename std::allocator_traits<alloc>;
+
+    using entity_traits = entt_traits<Entity>;
+    using sparse_container_type = std::vector<typename alloc_traits::pointer, typename alloc_traits::template rebind_alloc<typename alloc_traits::pointer>>;
+    using packed_container_type = std::vector<Entity, alloc>;
+
+    [[nodiscard]] auto *sparse_ptr(const Entity entt) const {
+        const auto pos = static_cast<size_type>(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<size_type>(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<size_type>(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;
+            }
+        }
+    }
+
+protected:
+    /**
+     * @brief Returns the element assigned to an entity.
+     * @return An opaque pointer to the element assigned to the entity.
+     */
+    virtual const void *get_at(const std::size_t) const ENTT_NOEXCEPT {
+        return nullptr;
+    }
+
+    /*! @brief Swaps two entities in a sparse set. */
+    virtual void swap_at(const std::size_t, const std::size_t) {}
+
+    /*! @brief Moves an entity in a sparse set. */
+    virtual void move_element(const std::size_t, const std::size_t) {}
+
+    /**
+     * @brief Erases an entity from a sparse set.
+     * @param pos A valid position of an element within a sparse set.
+     */
+    virtual void swap_and_pop(const std::size_t pos) {
+        sparse_ref(packed.back()) = entity_traits::combine(static_cast<typename entity_traits::entity_type>(pos), entity_traits::to_integral(packed.back()));
+        // lazy self-assignment guard
+        sparse_ref(packed[pos]) = null;
+        packed[pos] = packed.back();
+        // unnecessary but it helps to detect nasty bugs
+        ENTT_ASSERT((packed.back() = tombstone, true), "");
+        packed.pop_back();
+    }
+
+    /**
+     * @brief Erases an entity from a sparse set.
+     * @param pos A valid position of an element within a sparse set.
+     */
+    virtual void in_place_pop(const std::size_t pos) {
+        sparse_ref(packed[pos]) = null;
+        packed[pos] = std::exchange(free_list, entity_traits::combine(static_cast<typename entity_traits::entity_type>(pos), entity_traits::reserved));
+    }
+
+    /**
+     * @brief Assigns an entity to a sparse set.
+     * @param entt A valid identifier.
+     */
+    virtual void try_emplace(const Entity entt, const void * = nullptr) {
+        if(auto &elem = assure_at_least(entt); free_list == null) {
+            packed.push_back(entt);
+            elem = entity_traits::combine(static_cast<typename entity_traits::entity_type>(packed.size() - 1u), entity_traits::to_integral(entt));
+        } else {
+            elem = entity_traits::combine(entity_traits::to_integral(free_list), entity_traits::to_integral(entt));
+            free_list = std::exchange(packed[static_cast<size_type>(entity_traits::to_entity(free_list))], entt);
+        }
+    }
+
+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 = internal::sparse_set_iterator<packed_container_type>;
+    /*! @brief Constant random access iterator type. */
+    using const_iterator = iterator;
+    /*! @brief Reverse iterator type. */
+    using reverse_iterator = std::reverse_iterator<iterator>;
+    /*! @brief Constant reverse iterator type. */
+    using const_reverse_iterator = reverse_iterator;
+
+    /*! @brief Default constructor. */
+    basic_sparse_set()
+        : basic_sparse_set{type_id<void>()} {}
+
+    /**
+     * @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<void>(), 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<void>(), 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 Sets the deletion policy of a sparse set.
+     * @param pol The deletion policy of the sparse set.
+     */
+    void policy(const deletion_policy pol) ENTT_NOEXCEPT {
+        if(pol != mode && mode == deletion_policy::in_place) {
+            compact();
+        }
+
+        mode = pol;
+    }
+
+    /**
+     * @brief Returns the next slot available for insertion.
+     * @return The next slot available for insertion.
+     */
+    [[nodiscard]] size_type slot() const ENTT_NOEXCEPT {
+        return free_list == null ? packed.size() : static_cast<size_type>(entity_traits::to_entity(free_list));
+    }
+
+    /**
+     * @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<typename iterator::difference_type>(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 {
+        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<size_type>(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<void *>(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) {
+        try_emplace(entt, value);
+        return find(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<typename It>
+    iterator insert(It first, It last) {
+        auto it = first;
+
+        for(; it != last && free_list != null; ++it) {
+            emplace(*it);
+        }
+
+        reserve(packed.size() + std::distance(it, last));
+
+        for(; it != last; ++it) {
+            emplace(*it);
+        }
+
+        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) {
+        (mode == deletion_policy::in_place) ? in_place_pop(index(entt)) : swap_and_pop(index(entt));
+        ENTT_ASSERT(!contains(entt), "Destruction did not take place");
+    }
+
+    /**
+     * @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<typename It>
+    void erase(It first, It last) {
+        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<typename It>
+    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);
+                std::swap(packed[from], packed[to]);
+                const auto entity = static_cast<typename entity_traits::entity_type>(to);
+                sparse_ref(packed[to]) = entity_traits::combine(entity, entity_traits::to_integral(packed[to]));
+                *it = entity_traits::combine(static_cast<typename entity_traits::entity_type>(from), entity_traits::reserved);
+                for(; from && packed[from - 1u] == tombstone; --from) {}
+            }
+        }
+
+        free_list = tombstone;
+        packed.resize(from);
+    }
+
+    /**
+     * @copybrief swap_at
+     *
+     * 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<size_type>(from), static_cast<size_type>(to));
+        entt = entity_traits::combine(to, entity_traits::to_integral(packed[from]));
+        other = entity_traits::combine(from, entity_traits::to_integral(packed[to]));
+        std::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<typename Compare, typename Sort = std_sort, typename... Args>
+    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>(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<typename entity_traits::entity_type>(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<typename Compare, typename Sort = std_sort, typename... Args>
+    void sort(Compare compare, Sort algo = Sort{}, Args &&...args) {
+        compact();
+        sort_n(packed.size(), std::move(compare), std::move(algo), std::forward<Args>(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.<br/>
+     * 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() {
+        for(auto &&entity: *this) {
+            // honor the modality and filter all tombstones
+            remove(entity);
+        }
+    }
+
+    /**
+     * @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

+ 1091 - 0
Dependencies/include/entt/entity/storage.hpp

@@ -0,0 +1,1091 @@
+#ifndef ENTT_ENTITY_STORAGE_HPP
+#define ENTT_ENTITY_STORAGE_HPP
+
+#include <cstddef>
+#include <iterator>
+#include <memory>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <vector>
+#include "../config/config.h"
+#include "../core/algorithm.hpp"
+#include "../core/any.hpp"
+#include "../core/compressed_pair.hpp"
+#include "../core/iterator.hpp"
+#include "../core/memory.hpp"
+#include "../core/type_info.hpp"
+#include "../core/type_traits.hpp"
+#include "../signal/sigh.hpp"
+#include "component.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<typename Container>
+class storage_iterator final {
+    friend storage_iterator<const Container>;
+
+    using container_type = std::remove_const_t<Container>;
+    using allocator_traits = std::allocator_traits<typename container_type::allocator_type>;
+    using comp_traits = component_traits<typename container_type::value_type>;
+
+    using iterator_traits = std::iterator_traits<std::conditional_t<
+        std::is_const_v<Container>,
+        typename allocator_traits::template rebind_traits<typename std::pointer_traits<typename container_type::value_type>::element_type>::const_pointer,
+        typename allocator_traits::template rebind_traits<typename std::pointer_traits<typename container_type::value_type>::element_type>::pointer>>;
+
+public:
+    using difference_type = typename iterator_traits::difference_type;
+    using value_type = typename iterator_traits::value_type;
+    using pointer = typename iterator_traits::pointer;
+    using reference = typename iterator_traits::reference;
+    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<bool Const = std::is_const_v<Container>, typename = std::enable_if_t<Const>>
+    storage_iterator(const storage_iterator<std::remove_const_t<Container>> &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 = offset - value - 1;
+        return (*packed)[pos / comp_traits::page_size][fast_mod(pos, comp_traits::page_size)];
+    }
+
+    [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
+        const auto pos = offset - 1;
+        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;
+    }
+
+private:
+    Container *packed;
+    difference_type offset;
+};
+
+template<typename CLhs, typename CRhs>
+[[nodiscard]] auto operator-(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) ENTT_NOEXCEPT {
+    return rhs.index() - lhs.index();
+}
+
+template<typename CLhs, typename CRhs>
+[[nodiscard]] bool operator==(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) ENTT_NOEXCEPT {
+    return lhs.index() == rhs.index();
+}
+
+template<typename CLhs, typename CRhs>
+[[nodiscard]] bool operator!=(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) ENTT_NOEXCEPT {
+    return !(lhs == rhs);
+}
+
+template<typename CLhs, typename CRhs>
+[[nodiscard]] bool operator<(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) ENTT_NOEXCEPT {
+    return lhs.index() > rhs.index();
+}
+
+template<typename CLhs, typename CRhs>
+[[nodiscard]] bool operator>(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) ENTT_NOEXCEPT {
+    return lhs.index() < rhs.index();
+}
+
+template<typename CLhs, typename CRhs>
+[[nodiscard]] bool operator<=(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) ENTT_NOEXCEPT {
+    return !(lhs > rhs);
+}
+
+template<typename CLhs, typename CRhs>
+[[nodiscard]] bool operator>=(const storage_iterator<CLhs> &lhs, const storage_iterator<CRhs> &rhs) ENTT_NOEXCEPT {
+    return !(lhs < rhs);
+}
+
+template<typename It, typename... Other>
+class extended_storage_iterator final {
+    template<typename Iter, typename... Args>
+    friend class extended_storage_iterator;
+
+public:
+    using difference_type = typename std::iterator_traits<It>::difference_type;
+    using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval<It>()), std::forward_as_tuple(*std::declval<Other>()...)));
+    using pointer = input_iterator_pointer<value_type>;
+    using reference = value_type;
+    using iterator_category = std::input_iterator_tag;
+
+    extended_storage_iterator() = default;
+
+    extended_storage_iterator(It base, Other... other) ENTT_NOEXCEPT
+        : it{base, other...} {}
+
+    template<typename... Args, typename = std::enable_if_t<(!std::is_same_v<Other, Args> && ...) && (std::is_constructible_v<Other, Args> && ...)>>
+    extended_storage_iterator(const extended_storage_iterator<It, Args...> &other) ENTT_NOEXCEPT
+        : it{other.it} {}
+
+    extended_storage_iterator &operator++() ENTT_NOEXCEPT {
+        return ++std::get<It>(it), (++std::get<Other>(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>(it), *std::get<Other>(it)...};
+    }
+
+    template<typename... CLhs, typename... CRhs>
+    friend bool operator==(const extended_storage_iterator<CLhs...> &, const extended_storage_iterator<CRhs...> &) ENTT_NOEXCEPT;
+
+private:
+    std::tuple<It, Other...> it;
+};
+
+template<typename... CLhs, typename... CRhs>
+[[nodiscard]] bool operator==(const extended_storage_iterator<CLhs...> &lhs, const extended_storage_iterator<CRhs...> &rhs) ENTT_NOEXCEPT {
+    return std::get<0>(lhs.it) == std::get<0>(rhs.it);
+}
+
+template<typename... CLhs, typename... CRhs>
+[[nodiscard]] bool operator!=(const extended_storage_iterator<CLhs...> &lhs, const extended_storage_iterator<CRhs...> &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<typename Entity, typename Type, typename Allocator, typename>
+class basic_storage: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
+    using allocator_traits = std::allocator_traits<Allocator>;
+    using alloc = typename allocator_traits::template rebind_alloc<Type>;
+    using alloc_traits = typename std::allocator_traits<alloc>;
+
+    using comp_traits = component_traits<Type>;
+    using underlying_type = basic_sparse_set<Entity, typename allocator_traits::template rebind_alloc<Entity>>;
+    using container_type = std::vector<typename alloc_traits::pointer, typename alloc_traits::template rebind_alloc<typename alloc_traits::pointer>>;
+
+    [[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);
+    }
+
+    void shrink_to_size(const std::size_t sz) {
+        if(base_type::slot() == base_type::size()) {
+            for(auto pos = sz, last = base_type::size(); pos < last; ++pos) {
+                std::destroy_at(std::addressof(element_at(pos)));
+            }
+        } else {
+            for(auto pos = sz, last = base_type::size(); pos < last; ++pos) {
+                if(base_type::at(pos) != tombstone) {
+                    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);
+    }
+
+    template<typename... Args>
+    void construct(typename alloc_traits::pointer ptr, Args &&...args) {
+        if constexpr(std::is_aggregate_v<value_type>) {
+            alloc_traits::construct(packed.second(), to_address(ptr), Type{std::forward<Args>(args)...});
+        } else {
+            alloc_traits::construct(packed.second(), to_address(ptr), std::forward<Args>(args)...);
+        }
+    }
+
+    template<typename It, typename Generator>
+    void consume_range(It first, It last, Generator generator) {
+        for(const auto sz = base_type::size(); first != last && base_type::slot() != sz; ++first) {
+            emplace(*first, generator());
+        }
+
+        reserve(base_type::size() + std::distance(first, last));
+
+        for(; first != last; ++first) {
+            emplace(*first, generator());
+        }
+    }
+
+protected:
+    /**
+     * @brief Returns the element assigned to an entity.
+     * @param pos A valid position of an element within a storage.
+     * @return An opaque pointer to the element assigned to the entity.
+     */
+    const void *get_at(const std::size_t pos) const ENTT_NOEXCEPT override {
+        return std::addressof(element_at(pos));
+    }
+
+    /**
+     * @brief Swaps two elements in a storage.
+     * @param lhs A valid position of an element within a storage.
+     * @param rhs A valid position of an element within a storage.
+     */
+    void swap_at(const std::size_t lhs, const std::size_t rhs) final {
+        std::swap(element_at(lhs), element_at(rhs));
+    }
+
+    /**
+     * @brief Moves an element within a storage.
+     * @param from A valid position of an element within a storage.
+     * @param to A valid position of an element within a storage.
+     */
+    void move_element(const std::size_t from, const std::size_t to) final {
+        auto &elem = element_at(from);
+        construct(assure_at_least(to), std::move(elem));
+        std::destroy_at(std::addressof(elem));
+        base_type::move_element(from, to);
+    }
+
+    /**
+     * @brief Erases an element from a storage.
+     * @param pos A valid position of an element within a storage.
+     */
+    void swap_and_pop(const std::size_t pos) override {
+        auto &elem = element_at(base_type::size() - 1u);
+        // support for nosy destructors
+        [[maybe_unused]] auto unused = std::exchange(element_at(pos), std::move(elem));
+        base_type::swap_and_pop(pos);
+        std::destroy_at(std::addressof(elem));
+    }
+
+    /**
+     * @brief Erases an element from a storage.
+     * @param pos A valid position of an element within a storage.
+     */
+    void in_place_pop(const std::size_t pos) override {
+        auto &elem = element_at(pos);
+        base_type::in_place_pop(pos);
+        // support for nosy destructors
+        std::destroy_at(std::addressof(elem));
+    }
+
+    /**
+     * @brief Assigns an entity to a storage.
+     * @param entt A valid identifier.
+     * @param value Optional opaque value.
+     */
+    void try_emplace([[maybe_unused]] const Entity entt, const void *value) override {
+        if(value) {
+            if constexpr(std::is_copy_constructible_v<value_type>) {
+                emplace(entt, *static_cast<const value_type *>(value));
+            }
+        } else {
+            if constexpr(std::is_default_constructible_v<value_type>) {
+                emplace(entt);
+            }
+        }
+    }
+
+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<typename alloc_traits::const_pointer>::const_pointer;
+    /*! @brief Random access iterator type. */
+    using iterator = internal::storage_iterator<container_type>;
+    /*! @brief Constant random access iterator type. */
+    using const_iterator = internal::storage_iterator<const container_type>;
+    /*! @brief Reverse iterator type. */
+    using reverse_iterator = std::reverse_iterator<iterator>;
+    /*! @brief Constant reverse iterator type. */
+    using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+    /*! @brief Extended iterable storage proxy. */
+    using iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::iterator, iterator>>;
+    /*! @brief Constant extended iterable storage proxy. */
+    using const_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_iterator, const_iterator>>;
+
+    /*! @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<value_type>(), 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 sparse set 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<typename iterator::difference_type>(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<typename iterator::difference_type>(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<value_type &>(std::as_const(*this).get(entt));
+    }
+
+    /**
+     * @brief Returns the object assigned to an entity as a tuple.
+     *
+     * @sa get
+     *
+     * @param entt A valid identifier.
+     * @return The object assigned to the entity as a tuple.
+     */
+    [[nodiscard]] std::tuple<const value_type &> get_as_tuple(const entity_type entt) const ENTT_NOEXCEPT {
+        return std::forward_as_tuple(get(entt));
+    }
+
+    /*! @copydoc get_as_tuple */
+    [[nodiscard]] std::tuple<value_type &> 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<typename... Args>
+    value_type &emplace(const entity_type entt, Args &&...args) {
+        const auto pos = base_type::slot();
+        auto *elem = assure_at_least(pos);
+        construct(elem, std::forward<Args>(args)...);
+
+        ENTT_TRY {
+            base_type::try_emplace(entt);
+            ENTT_ASSERT(pos == base_type::index(entt), "Misplaced component");
+        }
+        ENTT_CATCH {
+            std::destroy_at(std::addressof(*elem));
+            ENTT_THROW;
+        }
+
+        return *elem;
+    }
+
+    /**
+     * @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<typename... Func>
+    value_type &patch(const entity_type entt, Func &&...func) {
+        const auto idx = base_type::index(entt);
+        auto &elem = element_at(idx);
+        (std::forward<Func>(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<typename It>
+    void insert(It first, It last, const value_type &value = {}) {
+        consume_range(std::move(first), std::move(last), [&value]() -> decltype(auto) { return 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<typename EIt, typename CIt, typename = std::enable_if_t<std::is_same_v<std::decay_t<typename std::iterator_traits<CIt>::value_type>, value_type>>>
+    void insert(EIt first, EIt last, CIt from) {
+        consume_range(std::move(first), std::move(last), [&from]() -> decltype(auto) { return *(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<container_type, alloc> packed;
+};
+
+/*! @copydoc basic_storage */
+template<typename Entity, typename Type, typename Allocator>
+class basic_storage<Entity, Type, Allocator, std::enable_if_t<ignore_as_empty_v<Type>>>
+    : public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
+    using allocator_traits = std::allocator_traits<Allocator>;
+    using comp_traits = component_traits<Type>;
+
+public:
+    /*! @brief Base type. */
+    using base_type = basic_sparse_set<Entity, typename allocator_traits::template rebind_alloc<Entity>>;
+    /*! @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<internal::extended_storage_iterator<typename base_type::iterator>>;
+    /*! @brief Constant extended iterable storage proxy. */
+    using const_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_iterator>>;
+
+    /*! @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<value_type>(), 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.
+     * @param args Parameters to use to construct an object for the entity.
+     */
+    template<typename... Args>
+    void emplace(const entity_type entt, Args &&...args) {
+        [[maybe_unused]] const value_type elem{std::forward<Args>(args)...};
+        base_type::try_emplace(entt);
+    }
+
+    /**
+    * @brief Updates the instance assigned to a given entity in-place.
+    * @tparam Func Types of the function objects to invoke.
+    * @param entt A valid identifier.
+    * @param func Valid function objects.
+    */
+    template<typename... Func>
+    void patch([[maybe_unused]] const entity_type entt, Func &&...func) {
+        ENTT_ASSERT(base_type::contains(entt), "Storage does not contain entity");
+        (std::forward<Func>(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<typename It, typename... Args>
+    void insert(It first, It last, Args &&...) {
+        for(const auto sz = base_type::size(); first != last && base_type::slot() != sz; ++first) {
+            emplace(*first);
+        }
+
+        base_type::reserve(base_type::size() + std::distance(first, last));
+
+        for(; first != last; ++first) {
+            emplace(*first);
+        }
+    }
+
+    /**
+     * @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 Mixin type used to add signal support to storage types.
+ *
+ * The function type of a listener is equivalent to:
+ *
+ * @code{.cpp}
+ * void(basic_registry<entity_type> &, entity_type);
+ * @endcode
+ *
+ * This applies to all signals made available.
+ *
+ * @tparam Type The type of the underlying storage.
+ */
+template<typename Type>
+class sigh_storage_mixin final: public Type {
+    /*! @copydoc basic_sparse_set::swap_and_pop */
+    void swap_and_pop(const std::size_t pos) final {
+        ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
+        const auto entt = Type::operator[](pos);
+        destruction.publish(*owner, entt);
+        Type::swap_and_pop(Type::index(entt));
+    }
+
+    /*! @copydoc basic_sparse_set::in_place_pop */
+    void in_place_pop(const std::size_t pos) final {
+        ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
+        const auto entt = Type::operator[](pos);
+        destruction.publish(*owner, entt);
+        Type::in_place_pop(Type::index(entt));
+    }
+
+    /*! @copydoc basic_sparse_set::try_emplace */
+    void try_emplace(const typename Type::entity_type entt, const void *value) final {
+        ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
+        Type::try_emplace(entt, value);
+
+        if(Type::contains(entt)) {
+            construction.publish(*owner, entt);
+        }
+    }
+
+public:
+    /*! @brief Underlying value type. */
+    using value_type = typename Type::value_type;
+    /*! @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.<br/>
+     * 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.<br/>
+     * 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.<br/>
+     * 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<typename... Args>
+    decltype(auto) emplace(const entity_type entt, Args &&...args) {
+        Type::emplace(entt, std::forward<Args>(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<typename... Func>
+    decltype(auto) patch(const entity_type entt, Func &&...func) {
+        Type::patch(entt, std::forward<Func>(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<typename It, typename... Args>
+    void insert(It first, It last, Args &&...args) {
+        Type::insert(first, last, std::forward<Args>(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<basic_registry<entity_type>>(&value);
+        owner = reg ? reg : owner;
+        Type::bind(std::move(value));
+    }
+
+private:
+    sigh<void(basic_registry<entity_type> &, const entity_type)> construction{};
+    sigh<void(basic_registry<entity_type> &, const entity_type)> destruction{};
+    sigh<void(basic_registry<entity_type> &, const entity_type)> update{};
+    basic_registry<entity_type> *owner{};
+};
+
+/**
+ * @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<typename Entity, typename Type, typename = void>
+struct storage_traits {
+    /*! @brief Resulting type after component-to-storage conversion. */
+    using storage_type = sigh_storage_mixin<basic_storage<Entity, Type>>;
+};
+
+} // namespace entt
+
+#endif

+ 52 - 0
Dependencies/include/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<typename... Type>
+struct exclude_t: type_list<Type...> {};
+
+/**
+ * @brief Variable template for exclusion lists.
+ * @tparam Type List of types.
+ */
+template<typename... Type>
+inline constexpr exclude_t<Type...> exclude{};
+
+/**
+ * @brief Alias for lists of observed components.
+ * @tparam Type List of types.
+ */
+template<typename... Type>
+struct get_t: type_list<Type...> {};
+
+/**
+ * @brief Variable template for lists of observed components.
+ * @tparam Type List of types.
+ */
+template<typename... Type>
+inline constexpr get_t<Type...> get{};
+
+/**
+ * @brief Alias for lists of owned components.
+ * @tparam Type List of types.
+ */
+template<typename... Type>
+struct owned_t: type_list<Type...> {};
+
+/**
+ * @brief Variable template for lists of owned components.
+ * @tparam Type List of types.
+ */
+template<typename... Type>
+inline constexpr owned_t<Type...> owned{};
+
+} // namespace entt
+
+#endif

+ 848 - 0
Dependencies/include/entt/entity/view.hpp

@@ -0,0 +1,848 @@
+#ifndef ENTT_ENTITY_VIEW_HPP
+#define ENTT_ENTITY_VIEW_HPP
+
+#include <algorithm>
+#include <array>
+#include <iterator>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#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<typename Type, std::size_t Component, std::size_t Exclude>
+class view_iterator final {
+    [[nodiscard]] bool valid() const {
+        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 iterator_type = typename Type::iterator;
+    using difference_type = typename std::iterator_traits<iterator_type>::difference_type;
+    using value_type = typename std::iterator_traits<iterator_type>::value_type;
+    using pointer = typename std::iterator_traits<iterator_type>::pointer;
+    using reference = typename std::iterator_traits<iterator_type>::reference;
+    using iterator_category = std::forward_iterator_tag;
+
+    view_iterator() ENTT_NOEXCEPT
+        : it{},
+          last{},
+          pools{},
+          filter{} {}
+
+    view_iterator(iterator_type curr, iterator_type to, std::array<const Type *, Component> all_of, std::array<const Type *, Exclude> 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 {
+        return &*it;
+    }
+
+    [[nodiscard]] reference operator*() const {
+        return *operator->();
+    }
+
+    template<typename LhsType, auto... LhsArgs, typename RhsType, auto... RhsArgs>
+    friend bool operator==(const view_iterator<LhsType, LhsArgs...> &, const view_iterator<RhsType, RhsArgs...> &) ENTT_NOEXCEPT;
+
+private:
+    iterator_type it;
+    iterator_type last;
+    std::array<const Type *, Component> pools;
+    std::array<const Type *, Exclude> filter;
+};
+
+template<typename LhsType, auto... LhsArgs, typename RhsType, auto... RhsArgs>
+[[nodiscard]] bool operator==(const view_iterator<LhsType, LhsArgs...> &lhs, const view_iterator<RhsType, RhsArgs...> &rhs) ENTT_NOEXCEPT {
+    return lhs.it == rhs.it;
+}
+
+template<typename LhsType, auto... LhsArgs, typename RhsType, auto... RhsArgs>
+[[nodiscard]] bool operator!=(const view_iterator<LhsType, LhsArgs...> &lhs, const view_iterator<RhsType, RhsArgs...> &rhs) ENTT_NOEXCEPT {
+    return !(lhs == rhs);
+}
+
+template<typename It, typename... Storage>
+struct extended_view_iterator final {
+    using difference_type = std::ptrdiff_t;
+    using value_type = decltype(std::tuple_cat(std::make_tuple(*std::declval<It>()), std::declval<Storage>().get_as_tuple({})...));
+    using pointer = input_iterator_pointer<value_type>;
+    using reference = value_type;
+    using iterator_category = std::input_iterator_tag;
+
+    extended_view_iterator() = default;
+
+    extended_view_iterator(It from, std::tuple<Storage *...> storage) ENTT_NOEXCEPT
+        : 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<typename... Lhs, typename... Rhs>
+    friend bool operator==(const extended_view_iterator<Lhs...> &, const extended_view_iterator<Rhs...> &) ENTT_NOEXCEPT;
+
+private:
+    It it;
+    std::tuple<Storage *...> pools;
+};
+
+template<typename... Lhs, typename... Rhs>
+[[nodiscard]] bool operator==(const extended_view_iterator<Lhs...> &lhs, const extended_view_iterator<Rhs...> &rhs) ENTT_NOEXCEPT {
+    return lhs.it == rhs.it;
+}
+
+template<typename... Lhs, typename... Rhs>
+[[nodiscard]] bool operator!=(const extended_view_iterator<Lhs...> &lhs, const extended_view_iterator<Rhs...> &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<typename, typename, typename, typename>
+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<typename Entity, typename... Component, typename... Exclude>
+class basic_view<Entity, get_t<Component...>, exclude_t<Exclude...>> {
+    template<typename, typename, typename, typename>
+    friend class basic_view;
+
+    template<typename Comp>
+    using storage_type = constness_as_t<typename storage_traits<Entity, std::remove_const_t<Comp>>::storage_type, Comp>;
+
+    template<std::size_t... Index>
+    [[nodiscard]] auto pools_to_array(std::index_sequence<Index...>) const ENTT_NOEXCEPT {
+        std::size_t pos{};
+        std::array<const base_type *, sizeof...(Component) - 1u> other{};
+        (static_cast<void>(std::get<Index>(pools) == view ? void() : void(other[pos++] = std::get<Index>(pools))), ...);
+        return other;
+    }
+
+    template<std::size_t Comp, std::size_t Other, typename... Args>
+    [[nodiscard]] auto dispatch_get(const std::tuple<Entity, Args...> &curr) const {
+        if constexpr(Comp == Other) {
+            return std::forward_as_tuple(std::get<Args>(curr)...);
+        } else {
+            return std::get<Other>(pools)->get_as_tuple(std::get<0>(curr));
+        }
+    }
+
+    template<std::size_t Comp, typename Func, std::size_t... Index>
+    void each(Func func, std::index_sequence<Index...>) const {
+        for(const auto curr: std::get<Comp>(pools)->each()) {
+            const auto entt = std::get<0>(curr);
+
+            if(((sizeof...(Component) != 1u) || (entt != tombstone))
+               && ((Comp == Index || std::get<Index>(pools)->contains(entt)) && ...)
+               && std::apply([entt](const auto *...cpool) { return (!cpool->contains(entt) && ...); }, filter)) {
+                if constexpr(is_applicable_v<Func, decltype(std::tuple_cat(std::tuple<entity_type>{}, std::declval<basic_view>().get({})))>) {
+                    std::apply(func, std::tuple_cat(std::make_tuple(entt), dispatch_get<Comp, Index>(curr)...));
+                } else {
+                    std::apply(func, std::tuple_cat(dispatch_get<Comp, Index>(curr)...));
+                }
+            }
+        }
+    }
+
+    template<typename Func, std::size_t... Index>
+    void pick_and_each(Func func, std::index_sequence<Index...> seq) const {
+        ((std::get<Index>(pools) == view ? each<Index>(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<typename storage_type<Component>::base_type...>;
+    /*! @brief Bidirectional iterator type. */
+    using iterator = internal::view_iterator<base_type, sizeof...(Component) - 1u, sizeof...(Exclude)>;
+    /*! @brief Iterable view type. */
+    using iterable = iterable_adaptor<internal::extended_view_iterator<iterator, storage_type<Component>...>>;
+
+    /*! @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> &...component, const storage_type<Exclude> &...epool) ENTT_NOEXCEPT
+        : pools{&component...},
+          filter{&epool...},
+          view{std::min<const base_type *>({&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<typename Comp>
+    [[nodiscard]] basic_view use() const ENTT_NOEXCEPT {
+        basic_view other{*this};
+        other.view = std::get<storage_type<Comp> *>(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<std::size_t Comp>
+    [[nodiscard]] basic_view use() const ENTT_NOEXCEPT {
+        basic_view other{*this};
+        other.view = std::get<Comp>(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<typename Comp>
+    [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT {
+        return *std::get<storage_type<Comp> *>(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<std::size_t Comp>
+    [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT {
+        return *std::get<Comp>(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 {
+        return iterator{view->begin(), view->end(), pools_to_array(std::index_sequence_for<Component...>{}), 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 {
+        return iterator{view->end(), view->end(), pools_to_array(std::index_sequence_for<Component...>{}), 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 {
+        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 {
+        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 {
+        return contains(entt) ? iterator{view->find(entt), view->end(), pools_to_array(std::index_sequence_for<Component...>{}), 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<Component...>(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 {
+        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<typename... Comp>
+    [[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<storage_type<Comp> *>(pools)->get(entt), ...);
+        } else {
+            return std::tuple_cat(std::get<storage_type<Comp> *>(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<std::size_t First, std::size_t... Other>
+    [[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<First>(pools)->get(entt);
+        } else {
+            return std::tuple_cat(std::get<First>(pools)->get_as_tuple(entt), std::get<Other>(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.<br/>
+     * 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<typename Func>
+    void each(Func func) const {
+        pick_and_each(std::move(func), std::index_sequence_for<Component...>{});
+    }
+
+    /**
+     * @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<typename... Get, typename... Excl>
+    [[nodiscard]] auto operator|(const basic_view<Entity, get_t<Get...>, exclude_t<Excl...>> &other) const ENTT_NOEXCEPT {
+        using view_type = basic_view<Entity, get_t<Component..., Get...>, exclude_t<Exclude..., Excl...>>;
+        return std::make_from_tuple<view_type>(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<const storage_type<Exclude> &>(*curr)...); }, filter),
+            std::apply([](const auto *...curr) { return std::forward_as_tuple(static_cast<const storage_type<Excl> &>(*curr)...); }, other.filter)));
+    }
+
+private:
+    std::tuple<storage_type<Component> *...> pools;
+    std::array<const base_type *, sizeof...(Exclude)> 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<typename Entity, typename Component>
+class basic_view<Entity, get_t<Component>, exclude_t<>, std::void_t<std::enable_if_t<!component_traits<std::remove_const_t<Component>>::in_place_delete>>> {
+    template<typename, typename, typename, typename>
+    friend class basic_view;
+
+    using storage_type = constness_as_t<typename storage_traits<Entity, std::remove_const_t<Component>>::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<storage_type>().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<typename... Comp>
+    [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT {
+        static_assert((std::is_same_v<Comp, Component> && ...), "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<std::size_t Comp>
+    [[nodiscard]] decltype(auto) storage() const ENTT_NOEXCEPT {
+        return *std::get<Comp>(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 {
+        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 {
+        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 {
+        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<Component>(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 {
+        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<typename... Comp>
+    [[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<Comp..., Component>, "Invalid component type");
+            return std::get<0>(pools)->get(entt);
+        }
+    }
+
+    /*! @copydoc get */
+    template<std::size_t Comp>
+    [[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.<br/>
+     * 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<typename Func>
+    void each(Func func) const {
+        if constexpr(ignore_as_empty_v<std::remove_const_t<Component>>) {
+            if constexpr(std::is_invocable_v<Func>) {
+                for(size_type pos{}, last = size(); pos < last; ++pos) {
+                    func();
+                }
+            } else {
+                for(auto entity: *view) {
+                    func(entity);
+                }
+            }
+        } else {
+            if constexpr(is_applicable_v<Func, decltype(*each().begin())>) {
+                for(const auto pack: each()) {
+                    std::apply(func, pack);
+                }
+            } else {
+                for(auto &&component: *std::get<0>(pools)) {
+                    func(component);
+                }
+            }
+        }
+    }
+
+    /**
+     * @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<typename... Get, typename... Excl>
+    [[nodiscard]] auto operator|(const basic_view<Entity, get_t<Get...>, exclude_t<Excl...>> &other) const ENTT_NOEXCEPT {
+        using view_type = basic_view<Entity, get_t<Component, Get...>, exclude_t<Excl...>>;
+        return std::make_from_tuple<view_type>(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<const typename view_type::template storage_type<Excl> &>(*curr)...); }, other.filter)));
+    }
+
+private:
+    std::tuple<storage_type *> pools;
+    std::array<const base_type *, 0u> 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<typename... Storage>
+basic_view(Storage &...storage) -> basic_view<std::common_type_t<typename Storage::entity_type...>, get_t<constness_as_t<typename Storage::value_type, Storage>...>, exclude_t<>>;
+
+} // namespace entt
+
+#endif

+ 57 - 0
Dependencies/include/entt/entt.hpp

@@ -0,0 +1,57 @@
+#include "config/version.h"
+#include "container/dense_hash_map.hpp"
+#include "container/dense_hash_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/component.hpp"
+#include "entity/entity.hpp"
+#include "entity/group.hpp"
+#include "entity/handle.hpp"
+#include "entity/helper.hpp"
+#include "entity/observer.hpp"
+#include "entity/organizer.hpp"
+#include "entity/registry.hpp"
+#include "entity/runtime_view.hpp"
+#include "entity/snapshot.hpp"
+#include "entity/sparse_set.hpp"
+#include "entity/storage.hpp"
+#include "entity/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 "signal/delegate.hpp"
+#include "signal/dispatcher.hpp"
+#include "signal/emitter.hpp"
+#include "signal/sigh.hpp"

+ 7 - 0
Dependencies/include/entt/fwd.hpp

@@ -0,0 +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"

+ 106 - 0
Dependencies/include/entt/locator/locator.hpp

@@ -0,0 +1,106 @@
+#ifndef ENTT_LOCATOR_LOCATOR_HPP
+#define ENTT_LOCATOR_LOCATOR_HPP
+
+#include <memory>
+#include <utility>
+#include "../config/config.h"
+
+namespace entt {
+
+/**
+ * @brief Service locator, nothing more.
+ *
+ * A service locator can be used to do what it promises: locate services.<br/>
+ * 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.
+ *
+ * @tparam Service Type of service managed by the locator.
+ */
+template<typename Service>
+struct service_locator {
+    /*! @brief Type of service offered. */
+    using service_type = Service;
+
+    /*! @brief Default constructor, deleted on purpose. */
+    service_locator() = delete;
+    /*! @brief Default destructor, deleted on purpose. */
+    ~service_locator() = delete;
+
+    /**
+     * @brief Tests if a valid service implementation is set.
+     * @return True if the service is set, false otherwise.
+     */
+    [[nodiscard]] static bool empty() ENTT_NOEXCEPT {
+        return !static_cast<bool>(service);
+    }
+
+    /**
+     * @brief Returns a weak pointer 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.
+     *
+     * @return A reference to the service implementation currently set, if any.
+     */
+    [[nodiscard]] static std::weak_ptr<Service> get() ENTT_NOEXCEPT {
+        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.
+     *
+     * @warning
+     * In case no service implementation has been set, a call to this function
+     * results in undefined behavior.
+     *
+     * @return A reference to the service implementation currently set, if any.
+     */
+    [[nodiscard]] static Service &ref() ENTT_NOEXCEPT {
+        return *service;
+    }
+
+    /**
+     * @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.
+     */
+    template<typename Impl = Service, typename... Args>
+    static void set(Args &&...args) {
+        service = std::make_shared<Impl>(std::forward<Args>(args)...);
+    }
+
+    /**
+     * @brief Sets or replaces a service.
+     * @param ptr Service to use to replace the current one.
+     */
+    static void set(std::shared_ptr<Service> ptr) {
+        ENTT_ASSERT(static_cast<bool>(ptr), "Null service not allowed");
+        service = std::move(ptr);
+    }
+
+    /**
+     * @brief Resets a service.
+     *
+     * The service is no longer valid after a reset.
+     */
+    static void reset() {
+        service.reset();
+    }
+
+private:
+    inline static std::shared_ptr<Service> service = nullptr;
+};
+
+} // namespace entt
+
+#endif

+ 35 - 0
Dependencies/include/entt/meta/adl_pointer.hpp

@@ -0,0 +1,35 @@
+#ifndef ENTT_META_ADL_POINTER_HPP
+#define ENTT_META_ADL_POINTER_HPP
+
+namespace entt {
+
+/**
+ * @brief ADL based lookup function for dereferencing meta pointer-like types.
+ * @tparam Type Element type.
+ * @param value A pointer-like object.
+ * @return The value returned from the dereferenced pointer.
+ */
+template<typename Type>
+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<typename Type>
+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

+ 274 - 0
Dependencies/include/entt/meta/container.hpp

@@ -0,0 +1,274 @@
+#ifndef ENTT_META_CONTAINER_HPP
+#define ENTT_META_CONTAINER_HPP
+
+#include <array>
+#include <map>
+#include <set>
+#include <type_traits>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+#include "../container/dense_hash_map.hpp"
+#include "../container/dense_hash_set.hpp"
+#include "meta.hpp"
+#include "type_traits.hpp"
+
+namespace entt {
+
+/**
+ * @cond TURN_OFF_DOXYGEN
+ * Internal details not to be documented.
+ */
+
+namespace internal {
+
+template<typename, typename = void>
+struct is_dynamic_sequence_container: std::false_type {};
+
+template<typename Type>
+struct is_dynamic_sequence_container<Type, std::void_t<decltype(&Type::reserve)>>: std::true_type {};
+
+template<typename, typename = void>
+struct is_key_only_meta_associative_container: std::true_type {};
+
+template<typename Type>
+struct is_key_only_meta_associative_container<Type, std::void_t<typename Type::mapped_type>>: std::false_type {};
+
+template<typename Type>
+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<const Type &>(container).size();
+    }
+
+    [[nodiscard]] static bool resize([[maybe_unused]] any &container, [[maybe_unused]] size_type sz) {
+        if constexpr(is_dynamic_sequence_container<Type>::value) {
+            if(auto *const cont = any_cast<Type>(&container); cont) {
+                cont->resize(sz);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    [[nodiscard]] static bool clear([[maybe_unused]] any &container) {
+        if constexpr(is_dynamic_sequence_container<Type>::value) {
+            if(auto *const cont = any_cast<Type>(&container); cont) {
+                cont->clear();
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    [[nodiscard]] static iterator begin(any &container) {
+        if(auto *const cont = any_cast<Type>(&container); cont) {
+            return iterator{std::begin(*cont)};
+        }
+
+        return iterator{std::begin(any_cast<const Type &>(container))};
+    }
+
+    [[nodiscard]] static iterator end(any &container) {
+        if(auto *const cont = any_cast<Type>(&container); cont) {
+            return iterator{std::end(*cont)};
+        }
+
+        return iterator{std::end(any_cast<const Type &>(container))};
+    }
+
+    [[nodiscard]] static iterator insert([[maybe_unused]] any &container, [[maybe_unused]] iterator it, [[maybe_unused]] meta_any &value) {
+        if constexpr(is_dynamic_sequence_container<Type>::value) {
+            if(auto *const cont = any_cast<Type>(&container); cont) {
+                // this abomination is necessary because only on macos value_type and const_reference are different types for std::vector<bool>
+                if(value.allow_cast<typename Type::const_reference>() || value.allow_cast<typename Type::value_type>()) {
+                    const auto *element = value.try_cast<std::remove_reference_t<typename Type::const_reference>>();
+                    return iterator{cont->insert(any_cast<const typename Type::iterator &>(it.base()), element ? *element : value.cast<typename Type::value_type>())};
+                }
+            }
+        }
+
+        return {};
+    }
+
+    [[nodiscard]] static iterator erase([[maybe_unused]] any &container, [[maybe_unused]] iterator it) {
+        if constexpr(is_dynamic_sequence_container<Type>::value) {
+            if(auto *const cont = any_cast<Type>(&container); cont) {
+                return iterator{cont->erase(any_cast<const typename Type::iterator &>(it.base()))};
+            }
+        }
+
+        return {};
+    }
+
+    [[nodiscard]] static meta_any get(any &container, size_type pos) {
+        if(auto *const cont = any_cast<Type>(&container); cont) {
+            return meta_any{std::in_place_type<typename Type::reference>, (*cont)[pos]};
+        }
+
+        return meta_any{std::in_place_type<typename Type::const_reference>, any_cast<const Type &>(container)[pos]};
+    }
+};
+
+template<typename Type>
+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<Type>::value;
+
+    [[nodiscard]] static size_type size(const any &container) ENTT_NOEXCEPT {
+        return any_cast<const Type &>(container).size();
+    }
+
+    [[nodiscard]] static bool clear(any &container) {
+        if(auto *const cont = any_cast<Type>(&container); cont) {
+            cont->clear();
+            return true;
+        }
+
+        return false;
+    }
+
+    [[nodiscard]] static iterator begin(any &container) {
+        if(auto *const cont = any_cast<Type>(&container); cont) {
+            return iterator{std::integral_constant<bool, key_only>{}, cont->begin()};
+        }
+
+        return iterator{std::integral_constant<bool, key_only>{}, std::begin(any_cast<const Type &>(container))};
+    }
+
+    [[nodiscard]] static iterator end(any &container) {
+        if(auto *const cont = any_cast<Type>(&container); cont) {
+            return iterator{std::integral_constant<bool, key_only>{}, cont->end()};
+        }
+
+        return iterator{std::integral_constant<bool, key_only>{}, std::end(any_cast<const Type &>(container))};
+    }
+
+    [[nodiscard]] static bool insert(any &container, meta_any &key, [[maybe_unused]] meta_any &value) {
+        if(auto *const cont = any_cast<Type>(&container); cont && key.allow_cast<const typename Type::key_type &>()) {
+            if constexpr(is_key_only_meta_associative_container<Type>::value) {
+                return cont->insert(key.cast<const typename Type::key_type &>()).second;
+            } else {
+                if(value.allow_cast<const typename Type::mapped_type &>()) {
+                    return cont->emplace(key.cast<const typename Type::key_type &>(), value.cast<const typename Type::mapped_type &>()).second;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    [[nodiscard]] static bool erase(any &container, meta_any &key) {
+        if(auto *const cont = any_cast<Type>(&container); cont && key.allow_cast<const typename Type::key_type &>()) {
+            return (cont->erase(key.cast<const typename Type::key_type &>()) != cont->size());
+        }
+
+        return false;
+    }
+
+    [[nodiscard]] static iterator find(any &container, meta_any &key) {
+        if(key.allow_cast<const typename Type::key_type &>()) {
+            if(auto *const cont = any_cast<Type>(&container); cont) {
+                return iterator{std::integral_constant<bool, key_only>{}, cont->find(key.cast<const typename Type::key_type &>())};
+            }
+
+            return iterator{std::integral_constant<bool, key_only>{}, any_cast<const Type &>(container).find(key.cast<const typename Type::key_type &>())};
+        }
+
+        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<typename Type, typename... Args>
+struct meta_sequence_container_traits<std::vector<Type, Args...>>
+    : internal::basic_meta_sequence_container_traits<std::vector<Type, Args...>> {};
+
+/**
+ * @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<typename Type, auto N>
+struct meta_sequence_container_traits<std::array<Type, N>>
+    : internal::basic_meta_sequence_container_traits<std::array<Type, N>> {};
+
+/**
+ * @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<typename Key, typename Value, typename... Args>
+struct meta_associative_container_traits<std::map<Key, Value, Args...>>
+    : internal::basic_meta_associative_container_traits<std::map<Key, Value, Args...>> {};
+
+/**
+ * @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<typename Key, typename Value, typename... Args>
+struct meta_associative_container_traits<std::unordered_map<Key, Value, Args...>>
+    : internal::basic_meta_associative_container_traits<std::unordered_map<Key, Value, Args...>> {};
+
+/**
+ * @brief Meta associative container traits for `std::set`s of any type.
+ * @tparam Key The type of elements.
+ * @tparam Args Other arguments.
+ */
+template<typename Key, typename... Args>
+struct meta_associative_container_traits<std::set<Key, Args...>>
+    : internal::basic_meta_associative_container_traits<std::set<Key, Args...>> {};
+
+/**
+ * @brief Meta associative container traits for `std::unordered_set`s of any
+ * type.
+ * @tparam Key The type of elements.
+ * @tparam Args Other arguments.
+ */
+template<typename Key, typename... Args>
+struct meta_associative_container_traits<std::unordered_set<Key, Args...>>
+    : internal::basic_meta_associative_container_traits<std::unordered_set<Key, Args...>> {};
+
+/**
+ * @brief Meta associative container traits for `dense_hash_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<typename Key, typename Type, typename... Args>
+struct meta_associative_container_traits<dense_hash_map<Key, Type, Args...>>
+    : internal::basic_meta_associative_container_traits<dense_hash_map<Key, Type, Args...>> {};
+
+/**
+ * @brief Meta associative container traits for `dense_hash_set`s of any type.
+ * @tparam Type The value type of the elements.
+ * @tparam Args Other arguments.
+ */
+template<typename Type, typename... Args>
+struct meta_associative_container_traits<dense_hash_set<Type, Args...>>
+    : internal::basic_meta_associative_container_traits<dense_hash_set<Type, Args...>> {};
+
+} // namespace entt
+
+#endif

+ 57 - 0
Dependencies/include/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

+ 652 - 0
Dependencies/include/entt/meta/factory.hpp

@@ -0,0 +1,652 @@
+#ifndef ENTT_META_FACTORY_HPP
+#define ENTT_META_FACTORY_HPP
+
+#include <algorithm>
+#include <cstddef>
+#include <functional>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#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"
+#include "range.hpp"
+#include "utility.hpp"
+
+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<typename...>
+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<typename Type, typename... Spec>
+class meta_factory<Type, Spec...>: public meta_factory<Type> {
+    void link_prop_if_required(internal::meta_prop_node &node) {
+        if(meta_range<internal::meta_prop_node *, internal::meta_prop_node> 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<std::size_t Step = 0, typename... Property, typename... Other>
+    void unroll(choice_t<2>, std::tuple<Property...> property, Other &&...other) {
+        std::apply([this](auto &&...curr) { (this->unroll<Step>(choice<2>, std::forward<Property>(curr)...)); }, property);
+        unroll<Step + sizeof...(Property)>(choice<2>, std::forward<Other>(other)...);
+    }
+
+    template<std::size_t Step = 0, typename... Property, typename... Other>
+    void unroll(choice_t<1>, std::pair<Property...> property, Other &&...other) {
+        assign<Step>(std::move(property.first), std::move(property.second));
+        unroll<Step + 1>(choice<2>, std::forward<Other>(other)...);
+    }
+
+    template<std::size_t Step = 0, typename Property, typename... Other>
+    void unroll(choice_t<0>, Property &&property, Other &&...other) {
+        assign<Step>(std::forward<Property>(property));
+        unroll<Step + 1>(choice<2>, std::forward<Other>(other)...);
+    }
+
+    template<std::size_t>
+    void unroll(choice_t<0>) {}
+
+    template<std::size_t = 0>
+    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<typename PropertyOrKey, typename... Value>
+    meta_factory<Type> prop(PropertyOrKey &&property_or_key, Value &&...value) {
+        if constexpr(sizeof...(Value) == 0) {
+            unroll(choice<2>, std::forward<PropertyOrKey>(property_or_key));
+        } else {
+            assign(std::forward<PropertyOrKey>(property_or_key), std::forward<Value>(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<typename... Property>
+    meta_factory<Type> props(Property... property) {
+        unroll(choice<2>, std::forward<Property>(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<typename Type>
+class meta_factory<Type> {
+    void link_base_if_required(internal::meta_base_node &node) {
+        if(meta_range<internal::meta_base_node *, internal::meta_base_node> 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) {
+        if(meta_range<internal::meta_conv_node *, internal::meta_conv_node> 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) {
+        if(meta_range<internal::meta_ctor_node *, internal::meta_ctor_node> 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) {
+        meta_range<internal::meta_data_node *, internal::meta_data_node> 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) {
+        node.id = id;
+
+        if(meta_range<internal::meta_func_node *, internal::meta_func_node> range{owner->func}; std::find(range.cbegin(), range.cend(), &node) == range.cend()) {
+            node.next = owner->func;
+            owner->func = &node;
+        }
+    }
+
+    template<typename Setter, auto Getter, typename Policy, std::size_t... Index>
+    auto data(const id_type id, std::index_sequence<Index...>) ENTT_NOEXCEPT {
+        using data_type = std::invoke_result_t<decltype(Getter), Type &>;
+        using args_type = type_list<typename meta_function_helper_t<Type, decltype(value_list_element_v<Index, Setter>)>::args_type...>;
+        static_assert(Policy::template value<data_type>, "Invalid return type for the given policy");
+
+        static internal::meta_data_node node{
+            {},
+            nullptr,
+            nullptr,
+            Setter::size,
+            /* this is never static */
+            (std::is_member_object_pointer_v<decltype(value_list_element_v<Index, Setter>)> && ... && std::is_const_v<std::remove_reference_t<data_type>>) ? internal::meta_traits::is_const : internal::meta_traits::is_none,
+            internal::meta_node<std::remove_const_t<std::remove_reference_t<data_type>>>::resolve(),
+            &meta_arg<type_list<type_list_element_t<type_list_element_t<Index, args_type>::size != 1u, type_list_element_t<Index, args_type>>...>>,
+            [](meta_handle instance, meta_any value) -> bool { return (meta_setter<Type, value_list_element_v<Index, Setter>>(*instance.operator->(), value.as_ref()) || ...); },
+            &meta_getter<Type, Getter, Policy>
+            // tricks clang-format
+        };
+
+        link_data_if_required(id, node);
+        return meta_factory<Type, Setter, std::integral_constant<decltype(Getter), Getter>>{&node.prop};
+    }
+
+public:
+    /*! @brief Default constructor. */
+    meta_factory()
+        : owner{internal::meta_node<Type>::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<Type>::value()) {
+        meta_range<internal::meta_type_node *, internal::meta_type_node> 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<Type, Type>{&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<typename Base>
+    auto base() ENTT_NOEXCEPT {
+        static_assert(std::is_base_of_v<Base, Type>, "Invalid base type");
+
+        static internal::meta_base_node node{
+            nullptr,
+            internal::meta_node<Base>::resolve(),
+            [](meta_any other) ENTT_NOEXCEPT -> meta_any {
+                if(auto *ptr = other.data(); ptr) {
+                    return forward_as_meta(*static_cast<Base *>(static_cast<Type *>(ptr)));
+                }
+
+                return forward_as_meta(*static_cast<const Base *>(static_cast<const Type *>(std::as_const(other).data())));
+            }
+            // tricks clang-format
+        };
+
+        link_base_if_required(node);
+        return meta_factory<Type>{};
+    }
+
+    /**
+     * @brief Assigns a meta conversion function to a meta type.
+     *
+     * Conversion functions can be either free functions or member
+     * functions.<br/>
+     * 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 Candidate>
+    auto conv() ENTT_NOEXCEPT {
+        static internal::meta_conv_node node{
+            nullptr,
+            internal::meta_node<std::remove_const_t<std::remove_reference_t<std::invoke_result_t<decltype(Candidate), Type &>>>>::resolve(),
+            [](const meta_any &instance) -> meta_any {
+                return forward_as_meta(std::invoke(Candidate, *static_cast<const Type *>(instance.data())));
+            }
+            // tricks clang-format
+        };
+
+        link_conv_if_required(node);
+        return meta_factory<Type>{};
+    }
+
+    /**
+     * @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<typename To>
+    auto conv() ENTT_NOEXCEPT {
+        static_assert(std::is_convertible_v<Type, To>, "Could not convert to the required type");
+
+        static internal::meta_conv_node node{
+            nullptr,
+            internal::meta_node<std::remove_const_t<std::remove_reference_t<To>>>::resolve(),
+            [](const meta_any &instance) -> meta_any { return forward_as_meta(static_cast<To>(*static_cast<const Type *>(instance.data()))); }
+            // tricks clang-format
+        };
+
+        link_conv_if_required(node);
+        return meta_factory<Type>{};
+    }
+
+    /**
+     * @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.<br/>
+     * 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 Candidate, typename Policy = as_is_t>
+    auto ctor() ENTT_NOEXCEPT {
+        using descriptor = meta_function_helper_t<Type, decltype(Candidate)>;
+        static_assert(Policy::template value<typename descriptor::return_type>, "Invalid return type for the given policy");
+        static_assert(std::is_same_v<std::decay_t<typename descriptor::return_type>, 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<typename descriptor::args_type>,
+            &meta_construct<Type, Candidate, Policy>
+            // tricks clang-format
+        };
+
+        link_ctor_if_required(node);
+        return meta_factory<Type>{};
+    }
+
+    /**
+     * @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<typename... Args>
+    auto ctor() ENTT_NOEXCEPT {
+        using descriptor = meta_function_helper_t<Type, Type (*)(Args...)>;
+
+        static internal::meta_ctor_node node{
+            nullptr,
+            descriptor::args_type::size,
+            &meta_arg<typename descriptor::args_type>,
+            &meta_construct<Type, Args...>
+            // tricks clang-format
+        };
+
+        link_ctor_if_required(node);
+        return meta_factory<Type>{};
+    }
+
+    /**
+     * @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.<br/>
+     * The signature of a free function should be identical to the following:
+     *
+     * @code{.cpp}
+     * void(Type &);
+     * @endcode
+     *
+     * Member functions should not take arguments instead.<br/>
+     * 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 Func>
+    auto dtor() ENTT_NOEXCEPT {
+        static_assert(std::is_invocable_v<decltype(Func), Type &>, "The function doesn't accept an object of the type provided");
+        owner->dtor = [](void *instance) { std::invoke(Func, *static_cast<Type *>(instance)); };
+        return meta_factory<Type>{};
+    }
+
+    /**
+     * @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.<br/>
+     * 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, typename Policy = as_is_t>
+    auto data(const id_type id) ENTT_NOEXCEPT {
+        if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
+            using data_type = std::remove_reference_t<std::invoke_result_t<decltype(Data), Type &>>;
+
+            static internal::meta_data_node node{
+                {},
+                nullptr,
+                nullptr,
+                1u,
+                /* this is never static */
+                std::is_const_v<data_type> ? internal::meta_traits::is_const : internal::meta_traits::is_none,
+                internal::meta_node<std::remove_const_t<data_type>>::resolve(),
+                &meta_arg<type_list<std::remove_const_t<data_type>>>,
+                &meta_setter<Type, Data>,
+                &meta_getter<Type, Data, Policy>
+                // tricks clang-format
+            };
+
+            link_data_if_required(id, node);
+            return meta_factory<Type, std::integral_constant<decltype(Data), Data>, std::integral_constant<decltype(Data), Data>>{&node.prop};
+        } else {
+            using data_type = std::remove_reference_t<std::remove_pointer_t<decltype(Data)>>;
+
+            static internal::meta_data_node node{
+                {},
+                nullptr,
+                nullptr,
+                1u,
+                ((std::is_same_v<Type, std::remove_const_t<data_type>> || std::is_const_v<data_type>) ? internal::meta_traits::is_const : internal::meta_traits::is_none) | internal::meta_traits::is_static,
+                internal::meta_node<std::remove_const_t<data_type>>::resolve(),
+                &meta_arg<type_list<std::remove_const_t<data_type>>>,
+                &meta_setter<Type, Data>,
+                &meta_getter<Type, Data, Policy>
+                // tricks clang-format
+            };
+
+            link_data_if_required(id, node);
+            return meta_factory<Type, std::integral_constant<decltype(Data), Data>>{&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.<br/>
+     * 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.<br/>
+     * 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 Setter, auto Getter, typename Policy = as_is_t>
+    auto data(const id_type id) ENTT_NOEXCEPT {
+        using data_type = std::invoke_result_t<decltype(Getter), Type &>;
+        static_assert(Policy::template value<data_type>, "Invalid return type for the given policy");
+
+        if constexpr(std::is_same_v<decltype(Setter), std::nullptr_t>) {
+            static internal::meta_data_node node{
+                {},
+                nullptr,
+                nullptr,
+                0u,
+                /* this is never static */
+                internal::meta_traits::is_const,
+                internal::meta_node<std::remove_const_t<std::remove_reference_t<data_type>>>::resolve(),
+                &meta_arg<type_list<>>,
+                &meta_setter<Type, Setter>,
+                &meta_getter<Type, Getter, Policy>
+                // tricks clang-format
+            };
+
+            link_data_if_required(id, node);
+            return meta_factory<Type, std::integral_constant<decltype(Setter), Setter>, std::integral_constant<decltype(Getter), Getter>>{&node.prop};
+        } else {
+            using args_type = typename meta_function_helper_t<Type, decltype(Setter)>::args_type;
+
+            static internal::meta_data_node node{
+                {},
+                nullptr,
+                nullptr,
+                1u,
+                /* this is never static nor const */
+                internal::meta_traits::is_none,
+                internal::meta_node<std::remove_const_t<std::remove_reference_t<data_type>>>::resolve(),
+                &meta_arg<type_list<type_list_element_t<args_type::size != 1u, args_type>>>,
+                &meta_setter<Type, Setter>,
+                &meta_getter<Type, Getter, Policy>
+                // tricks clang-format
+            };
+
+            link_data_if_required(id, node);
+            return meta_factory<Type, std::integral_constant<decltype(Setter), Setter>, std::integral_constant<decltype(Getter), Getter>>{&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.<br/>
+     * 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<typename Setter, auto Getter, typename Policy = as_is_t>
+    auto data(const id_type id) ENTT_NOEXCEPT {
+        return data<Setter, Getter, Policy>(id, std::make_index_sequence<Setter::size>{});
+    }
+
+    /**
+     * @brief Assigns a meta funcion to a meta type.
+     *
+     * Both member functions and free functions can be assigned to a meta
+     * type.<br/>
+     * 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 Candidate, typename Policy = as_is_t>
+    auto func(const id_type id) ENTT_NOEXCEPT {
+        using descriptor = meta_function_helper_t<Type, decltype(Candidate)>;
+        static_assert(Policy::template value<typename descriptor::return_type>, "Invalid return type for the given policy");
+
+        static internal::meta_func_node node{
+            {},
+            nullptr,
+            nullptr,
+            descriptor::args_type::size,
+            (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),
+            internal::meta_node<std::conditional_t<std::is_same_v<Policy, as_void_t>, void, std::remove_const_t<std::remove_reference_t<typename descriptor::return_type>>>>::resolve(),
+            &meta_arg<typename descriptor::args_type>,
+            &meta_invoke<Type, Candidate, Policy>
+            // tricks clang-format
+        };
+
+        link_func_if_required(id, node);
+        return meta_factory<Type, std::integral_constant<decltype(Candidate), Candidate>>{&node.prop};
+    }
+
+private:
+    internal::meta_type_node *owner;
+};
+
+/**
+ * @brief Utility function to use for reflection.
+ *
+ * This is the point from which everything starts.<br/>
+ * 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<typename Type>
+[[nodiscard]] auto meta() ENTT_NOEXCEPT {
+    auto *const node = internal::meta_node<Type>::resolve();
+    // extended meta factory to allow assigning properties to opaque meta types
+    return meta_factory<Type, Type>{&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.<br/>
+ * 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<typename Type>
+void meta_reset() ENTT_NOEXCEPT {
+    meta_reset(internal::meta_node<Type>::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

+ 24 - 0
Dependencies/include/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

+ 1869 - 0
Dependencies/include/entt/meta/meta.hpp

@@ -0,0 +1,1869 @@
+#ifndef ENTT_META_META_HPP
+#define ENTT_META_META_HPP
+
+#include <cstddef>
+#include <iterator>
+#include <memory>
+#include <type_traits>
+#include <utility>
+#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 "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<typename Type>
+    meta_sequence_container(std::in_place_type_t<Type>, any instance) ENTT_NOEXCEPT
+        : value_type_node{internal::meta_node<std::remove_const_t<std::remove_reference_t<typename Type::value_type>>>::resolve()},
+          size_fn{&meta_sequence_container_traits<Type>::size},
+          resize_fn{&meta_sequence_container_traits<Type>::resize},
+          clear_fn{&meta_sequence_container_traits<Type>::clear},
+          begin_fn{&meta_sequence_container_traits<Type>::begin},
+          end_fn{&meta_sequence_container_traits<Type>::end},
+          insert_fn{&meta_sequence_container_traits<Type>::insert},
+          erase_fn{&meta_sequence_container_traits<Type>::erase},
+          get_fn{&meta_sequence_container_traits<Type>::get},
+          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;
+    bool (*clear_fn)(any &) = nullptr;
+    iterator (*begin_fn)(any &) = nullptr;
+    iterator (*end_fn)(any &) = nullptr;
+    iterator (*insert_fn)(any &, iterator, meta_any &) = nullptr;
+    iterator (*erase_fn)(any &, iterator) = nullptr;
+    meta_any (*get_fn)(any &, size_type) = 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<typename Type>
+    meta_associative_container(std::in_place_type_t<Type>, any instance) ENTT_NOEXCEPT
+        : key_only_container{meta_associative_container_traits<Type>::key_only},
+          key_type_node{internal::meta_node<std::remove_const_t<std::remove_reference_t<typename Type::key_type>>>::resolve()},
+          mapped_type_node{nullptr},
+          value_type_node{internal::meta_node<std::remove_const_t<std::remove_reference_t<typename Type::value_type>>>::resolve()},
+          size_fn{&meta_associative_container_traits<Type>::size},
+          clear_fn{&meta_associative_container_traits<Type>::clear},
+          begin_fn{&meta_associative_container_traits<Type>::begin},
+          end_fn{&meta_associative_container_traits<Type>::end},
+          insert_fn{&meta_associative_container_traits<Type>::insert},
+          erase_fn{&meta_associative_container_traits<Type>::erase},
+          find_fn{&meta_associative_container_traits<Type>::find},
+          storage{std::move(instance)} {
+        if constexpr(!meta_associative_container_traits<Type>::key_only) {
+            mapped_type_node = internal::meta_node<std::remove_const_t<std::remove_reference_t<typename Type::mapped_type>>>::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 (*begin_fn)(any &) = nullptr;
+    iterator (*end_fn)(any &) = 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<typename Type>
+    static void basic_vtable([[maybe_unused]] const operation op, [[maybe_unused]] const any &value, [[maybe_unused]] void *other) {
+        static_assert(std::is_same_v<std::remove_reference_t<std::remove_const_t<Type>>, Type>, "Invalid type");
+
+        if constexpr(!std::is_void_v<Type>) {
+            switch(op) {
+            case operation::deref:
+                if constexpr(is_meta_pointer_like_v<Type>) {
+                    if constexpr(std::is_function_v<std::remove_const_t<typename std::pointer_traits<Type>::element_type>>) {
+                        *static_cast<meta_any *>(other) = any_cast<Type>(value);
+                    } else if constexpr(!std::is_same_v<std::remove_const_t<typename std::pointer_traits<Type>::element_type>, void>) {
+                        using in_place_type = decltype(adl_meta_pointer_like<Type>::dereference(any_cast<const Type &>(value)));
+                        static_cast<meta_any *>(other)->emplace<in_place_type>(adl_meta_pointer_like<Type>::dereference(any_cast<const Type &>(value)));
+                    }
+                }
+                break;
+            case operation::seq:
+                if constexpr(is_complete_v<meta_sequence_container_traits<Type>>) {
+                    *static_cast<meta_sequence_container *>(other) = {std::in_place_type<Type>, std::move(const_cast<any &>(value))};
+                }
+                break;
+            case operation::assoc:
+                if constexpr(is_complete_v<meta_associative_container_traits<Type>>) {
+                    *static_cast<meta_associative_container *>(other) = {std::in_place_type<Type>, std::move(const_cast<any &>(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<void>} {}
+
+public:
+    /*! @brief Default constructor. */
+    meta_any() ENTT_NOEXCEPT
+        : storage{},
+          node{},
+          vtable{&basic_vtable<void>} {}
+
+    /**
+     * @brief Constructs a wrapper by directly initializing the new object.
+     * @tparam Type Type of object to use to initialize the wrapper.
+     * @tparam Args Types of arguments to use to construct the new instance.
+     * @param args Parameters to use to construct the instance.
+     */
+    template<typename Type, typename... Args>
+    explicit meta_any(std::in_place_type_t<Type>, Args &&...args)
+        : storage{std::in_place_type<Type>, std::forward<Args>(args)...},
+          node{internal::meta_node<std::remove_const_t<std::remove_reference_t<Type>>>::resolve()},
+          vtable{&basic_vtable<std::remove_const_t<std::remove_reference_t<Type>>>} {}
+
+    /**
+     * @brief Constructs a wrapper from a given value.
+     * @tparam Type Type of object to use to initialize the wrapper.
+     * @param value An instance of an object to use to initialize the wrapper.
+     */
+    template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Type>, meta_any>>>
+    meta_any(Type &&value)
+        : meta_any{std::in_place_type<std::remove_const_t<std::remove_reference_t<Type>>>, std::forward<Type>(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<void>)} {}
+
+    /*! @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<void>);
+        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<typename Type>
+    std::enable_if_t<!std::is_same_v<std::decay_t<Type>, meta_any>, meta_any &>
+    operator=(Type &&value) {
+        emplace<std::decay_t<Type>>(std::forward<Type>(value));
+        return *this;
+    }
+
+    /**
+     * @brief Returns the type of the underlying object.
+     * @return The type of the underlying object, if any.
+     */
+    [[nodiscard]] inline meta_type type() const ENTT_NOEXCEPT;
+
+    /**
+     * @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 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<typename... Args>
+    meta_any invoke(const id_type id, Args &&...args) const;
+
+    /*! @copydoc invoke */
+    template<typename... Args>
+    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<typename Type>
+    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<typename Type>
+    [[nodiscard]] const Type *try_cast() const {
+        if(const auto &info = type_id<Type>(); node && *node->info == info) {
+            return any_cast<Type>(&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<Type>(); base) {
+                    return base;
+                }
+            }
+        }
+
+        return nullptr;
+    }
+
+    /*! @copydoc try_cast */
+    template<typename Type>
+    [[nodiscard]] Type *try_cast() {
+        if(const auto &info = type_id<Type>(); node && *node->info == info) {
+            return any_cast<Type>(&storage);
+        } else if(node) {
+            for(auto *it = node->base; it; it = it->next) {
+                if(Type *base = it->cast(as_ref()).template try_cast<Type>(); 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<typename Type>
+    [[nodiscard]] Type cast() const {
+        auto *const instance = try_cast<std::remove_reference_t<Type>>();
+        ENTT_ASSERT(instance, "Invalid instance");
+        return static_cast<Type>(*instance);
+    }
+
+    /*! @copydoc cast */
+    template<typename Type>
+    [[nodiscard]] Type cast() {
+        // forces const on non-reference types to make them work also with wrappers for const references
+        auto *const instance = try_cast<std::remove_reference_t<const Type>>();
+        ENTT_ASSERT(instance, "Invalid instance");
+        return static_cast<Type>(*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<typename Type>
+    [[nodiscard]] meta_any allow_cast() const {
+        const auto other = allow_cast(internal::meta_node<std::remove_const_t<std::remove_reference_t<Type>>>::resolve());
+
+        if constexpr(std::is_reference_v<Type> && !std::is_const_v<std::remove_reference_t<Type>>) {
+            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<typename Type>
+    bool allow_cast() {
+        if(auto other = std::as_const(*this).allow_cast(internal::meta_node<std::remove_const_t<std::remove_reference_t<Type>>>::resolve()); other) {
+            if(other.storage.owner()) {
+                std::swap(*this, other);
+                return true;
+            }
+
+            return (static_cast<constness_as_t<any, std::remove_reference_t<const Type>> &>(storage).data() != nullptr);
+        }
+
+        return false;
+    }
+
+    /**
+     * @brief Replaces the contained object by creating a new instance directly.
+     * @tparam Type Type of object to use to initialize the wrapper.
+     * @tparam Args Types of arguments to use to construct the new instance.
+     * @param args Parameters to use to construct the instance.
+     */
+    template<typename Type, typename... Args>
+    void emplace(Args &&...args) {
+        release();
+        vtable = &basic_vtable<std::remove_const_t<std::remove_reference_t<Type>>>;
+        storage.emplace<Type>(std::forward<Args>(args)...);
+        node = internal::meta_node<std::remove_const_t<std::remove_reference_t<Type>>>::resolve();
+    }
+
+    /**
+     * @brief Copy 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 meta_any &other);
+
+    /**
+     * @brief Move 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(meta_any &&other);
+
+    /*! @brief Destroys contained object */
+    void reset() {
+        release();
+        vtable = &basic_vtable<void>;
+        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);
+    }
+
+    /**
+     * @brief Checks if two wrappers differ in their content.
+     * @param other Wrapper with which to compare.
+     * @return False if the two objects differ in their content, true otherwise.
+     */
+    [[nodiscard]] bool operator==(const meta_any &other) const {
+        return (!node && !other.node) || (node && other.node && *node->info == *other.node->info && storage == other.storage);
+    }
+
+    /**
+     * @brief Aliasing constructor.
+     * @return A wrapper that shares a reference to an unmanaged object.
+     */
+    [[nodiscard]] meta_any as_ref() ENTT_NOEXCEPT {
+        return meta_any{*this, storage.as_ref()};
+    }
+
+    /*! @copydoc as_ref */
+    [[nodiscard]] meta_any as_ref() const ENTT_NOEXCEPT {
+        return meta_any{*this, storage.as_ref()};
+    }
+
+    /**
+     * @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 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<typename Type, typename... Args>
+meta_any make_meta(Args &&...args) {
+    return meta_any{std::in_place_type<Type>, std::forward<Args>(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<typename Type>
+meta_any forward_as_meta(Type &&value) {
+    return meta_any{std::in_place_type<std::conditional_t<std::is_rvalue_reference_v<Type>, std::decay_t<Type>, Type>>, std::forward<Type>(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.<br/>
+ * 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<typename Type, typename = std::enable_if_t<!std::is_same_v<std::decay_t<Type>, meta_handle>>>
+    meta_handle(Type &value) ENTT_NOEXCEPT
+        : meta_handle{} {
+        if constexpr(std::is_same_v<std::decay_t<Type>, meta_any>) {
+            any = value.as_ref();
+        } else {
+            any.emplace<Type &>(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<bool>(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.<br/>
+     * 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<typename Type>
+    bool set(meta_handle instance, Type &&value) const {
+        return node->set && node->set(std::move(instance), std::forward<Type>(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<meta_prop> 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.<br/>
+     * 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<typename... Args>
+    meta_any invoke(meta_handle instance, Args &&...args) const {
+        meta_any arguments[sizeof...(Args) + 1u]{std::forward<Args>(args)...};
+        return invoke(std::move(instance), arguments, sizeof...(Args));
+    }
+
+    /*! @copydoc meta_data::prop */
+    [[nodiscard]] meta_range<meta_prop> 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<auto Member, typename Pred>
+    [[nodiscard]] std::decay_t<decltype(std::declval<internal::meta_type_node>().*Member)> lookup(meta_any *const args, const typename internal::meta_type_node::size_type sz, Pred pred) const {
+        std::decay_t<decltype(node->*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 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<meta_type, internal::meta_base_node> 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<meta_data> 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<meta_func> func() const ENTT_NOEXCEPT {
+        return node->func;
+    }
+
+    /**
+     * @brief Lookup function for registered meta functions.
+     *
+     * Registered meta functions of base classes will also be visited.<br/>
+     * 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.<br/>
+     * 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<typename... Args>
+    [[nodiscard]] meta_any construct(Args &&...args) const {
+        meta_any arguments[sizeof...(Args) + 1u]{std::forward<Args>(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<typename... Args>
+    meta_any invoke(const id_type id, meta_handle instance, Args &&...args) const {
+        meta_any arguments[sizeof...(Args) + 1u]{std::forward<Args>(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.<br/>
+     * 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<typename Type>
+    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<Type>(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<meta_prop> 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<typename... Args>
+meta_any meta_any::invoke(const id_type id, Args &&...args) const {
+    return type().invoke(id, *this, std::forward<Args>(args)...);
+}
+
+template<typename... Args>
+meta_any meta_any::invoke(const id_type id, Args &&...args) {
+    return type().invoke(id, *this, std::forward<Args>(args)...);
+}
+
+template<typename Type>
+bool meta_any::set(const id_type id, Type &&value) {
+    return type().set(id, *this, std::forward<Type>(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{};
+}
+
+/*! @brief Opaque iterator for sequence containers. */
+class meta_sequence_container::meta_iterator {
+    enum class operation : std::uint8_t {
+        incr,
+        deref
+    };
+
+    using vtable_type = void(const operation, const any &, void *);
+
+    template<typename It>
+    static void basic_vtable(const operation op, const any &value, void *other) {
+        switch(op) {
+        case operation::incr:
+            ++any_cast<It &>(const_cast<any &>(value));
+            break;
+        case operation::deref:
+            static_cast<meta_any *>(other)->emplace<typename std::iterator_traits<It>::reference>(*any_cast<const It &>(value));
+            break;
+        }
+    }
+
+public:
+    /*! @brief Signed integer type. */
+    using difference_type = std::ptrdiff_t;
+    /*! @brief Type of elements returned by the iterator. */
+    using value_type = meta_any;
+    /*! @brief Pointer type, it's a _safe_ temporary object. */
+    using pointer = input_iterator_pointer<value_type>;
+    /*! @brief Reference type, it's **not** an actual reference. */
+    using reference = value_type;
+    /*! @brief Iterator category. */
+    using iterator_category = std::input_iterator_tag;
+
+    /*! @brief Default constructor. */
+    meta_iterator() ENTT_NOEXCEPT = default;
+
+    /**
+     * @brief Constructs a meta iterator from a given iterator.
+     * @tparam It Type of actual iterator with which to build the meta iterator.
+     * @param iter The actual iterator with which to build the meta iterator.
+     */
+    template<typename It>
+    explicit meta_iterator(It iter)
+        : vtable{&basic_vtable<It>},
+          handle{std::move(iter)} {}
+
+    /*! @brief Pre-increment operator. @return This iterator. */
+    meta_iterator &operator++() ENTT_NOEXCEPT {
+        return vtable(operation::incr, handle, nullptr), *this;
+    }
+
+    /*! @brief Post-increment operator. @return This iterator. */
+    meta_iterator operator++(int) ENTT_NOEXCEPT {
+        meta_iterator orig = *this;
+        return ++(*this), orig;
+    }
+
+    /**
+     * @brief Indirection operator for accessing the pointed opaque object.
+     * @return The element to which the iterator points.
+     */
+    [[nodiscard]] reference operator*() const {
+        meta_any other;
+        vtable(operation::deref, handle, &other);
+        return other;
+    }
+
+    /**
+     * @brief Access operator for accessing the pointed opaque object.
+     * @return The element to which the iterator points.
+     */
+    [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
+        return operator*();
+    }
+
+    /**
+     * @brief Returns false if an iterator is invalid, true otherwise.
+     * @return False if the iterator is invalid, true otherwise.
+     */
+    [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
+        return static_cast<bool>(handle);
+    }
+
+    /**
+     * @brief Checks if two iterators refer to the same element.
+     * @param other The iterator with which to compare.
+     * @return True if the iterators refer to the same element, false otherwise.
+     */
+    [[nodiscard]] bool operator==(const meta_iterator &other) const ENTT_NOEXCEPT {
+        return handle == other.handle;
+    }
+
+    /**
+     * @brief Checks if two iterators refer to the same element.
+     * @param other The iterator with which to compare.
+     * @return False if the iterators refer to the same element, true otherwise.
+     */
+    [[nodiscard]] bool operator!=(const meta_iterator &other) const ENTT_NOEXCEPT {
+        return !(*this == other);
+    }
+
+    /**
+     * @brief Returns the underlying iterator.
+     * @return The underlying iterator.
+     */
+    any base() const ENTT_NOEXCEPT {
+        return handle.as_ref();
+    }
+
+private:
+    vtable_type *vtable{};
+    any handle{};
+};
+
+/**
+ * @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 clear_fn(storage);
+}
+
+/**
+ * @brief Returns an iterator to the first element of a container.
+ * @return An iterator to the first element of the container.
+ */
+[[nodiscard]] inline meta_sequence_container::iterator meta_sequence_container::begin() {
+    return begin_fn(storage);
+}
+
+/**
+ * @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 end_fn(storage);
+}
+
+/**
+ * @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, 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);
+}
+
+/**
+ * @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) {
+    return get_fn(storage, pos);
+}
+
+/**
+ * @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<bool>(storage);
+}
+
+/*! @brief Opaque iterator for associative containers. */
+class meta_associative_container::meta_iterator {
+    enum class operation : std::uint8_t {
+        incr,
+        deref
+    };
+
+    using vtable_type = void(const operation, const any &, void *);
+
+    template<bool KeyOnly, typename It>
+    static void basic_vtable(const operation op, const any &value, void *other) {
+        switch(op) {
+        case operation::incr:
+            ++any_cast<It &>(const_cast<any &>(value));
+            break;
+        case operation::deref:
+            const auto &it = any_cast<const It &>(value);
+            if constexpr(KeyOnly) {
+                static_cast<std::pair<meta_any, meta_any> *>(other)->first.emplace<decltype(*it)>(*it);
+            } else {
+                static_cast<std::pair<meta_any, meta_any> *>(other)->first.emplace<decltype((it->first))>(it->first);
+                static_cast<std::pair<meta_any, meta_any> *>(other)->second.emplace<decltype((it->second))>(it->second);
+            }
+            break;
+        }
+    }
+
+public:
+    /*! @brief Signed integer type. */
+    using difference_type = std::ptrdiff_t;
+    /*! @brief Type of elements returned by the iterator. */
+    using value_type = std::pair<meta_any, meta_any>;
+    /*! @brief Pointer type, it's a _safe_ temporary object. */
+    using pointer = input_iterator_pointer<value_type>;
+    /*! @brief Reference type, it's **not** an actual reference. */
+    using reference = value_type;
+    /*! @brief Iterator category. */
+    using iterator_category = std::input_iterator_tag;
+
+    /*! @brief Default constructor. */
+    meta_iterator() ENTT_NOEXCEPT = default;
+
+    /**
+     * @brief Constructs an meta iterator from a given iterator.
+     * @tparam KeyOnly True if the container is also key-only, false otherwise.
+     * @tparam It Type of actual iterator with which to build the meta iterator.
+     * @param iter The actual iterator with which to build the meta iterator.
+     */
+    template<bool KeyOnly, typename It>
+    meta_iterator(std::integral_constant<bool, KeyOnly>, It iter)
+        : vtable{&basic_vtable<KeyOnly, It>},
+          handle{std::move(iter)} {}
+
+    /*! @brief Pre-increment operator. @return This iterator. */
+    meta_iterator &operator++() ENTT_NOEXCEPT {
+        return vtable(operation::incr, handle, nullptr), *this;
+    }
+
+    /*! @brief Post-increment operator. @return This iterator. */
+    meta_iterator operator++(int) ENTT_NOEXCEPT {
+        meta_iterator orig = *this;
+        return ++(*this), orig;
+    }
+
+    /**
+     * @brief Indirection operator for accessing the pointed opaque object.
+     * @return The element to which the iterator points.
+     */
+    [[nodiscard]] reference operator*() const {
+        reference other;
+        vtable(operation::deref, handle, &other);
+        return other;
+    }
+
+    /**
+     * @brief Access operator for accessing the pointed opaque object.
+     * @return The element to which the iterator points.
+     */
+    [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
+        return operator*();
+    }
+
+    /**
+     * @brief Returns false if an iterator is invalid, true otherwise.
+     * @return False if the iterator is invalid, true otherwise.
+     */
+    [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
+        return static_cast<bool>(handle);
+    }
+
+    /**
+     * @brief Checks if two iterators refer to the same element.
+     * @param other The iterator with which to compare.
+     * @return True if the iterators refer to the same element, false otherwise.
+     */
+    [[nodiscard]] bool operator==(const meta_iterator &other) const ENTT_NOEXCEPT {
+        return handle == other.handle;
+    }
+
+    /**
+     * @brief Checks if two iterators refer to the same element.
+     * @param other The iterator with which to compare.
+     * @return False if the iterators refer to the same element, true otherwise.
+     */
+    [[nodiscard]] bool operator!=(const meta_iterator &other) const ENTT_NOEXCEPT {
+        return !(*this == other);
+    }
+
+private:
+    vtable_type *vtable{};
+    any handle{};
+};
+
+/**
+ * @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 begin_fn(storage);
+}
+
+/*! @copydoc meta_sequence_container::end */
+[[nodiscard]] inline meta_associative_container::iterator meta_associative_container::end() {
+    return end_fn(storage);
+}
+
+/**
+ * @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<bool>(storage);
+}
+
+} // namespace entt
+
+#endif

+ 235 - 0
Dependencies/include/entt/meta/node.hpp

@@ -0,0 +1,235 @@
+#ifndef ENTT_META_NODE_HPP
+#define ENTT_META_NODE_HPP
+
+#include <cstddef>
+#include <type_traits>
+#include <utility>
+#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;
+    meta_data_node *next;
+    meta_prop_node *prop;
+    const size_type arity;
+    const meta_traits traits;
+    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;
+    meta_func_node *next;
+    meta_prop_node *prop;
+    const size_type arity;
+    const meta_traits traits;
+    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;
+    meta_type_node *next;
+    meta_prop_node *prop;
+    const size_type size_of;
+    const meta_traits traits;
+    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<typename... Args>
+meta_type_node *meta_arg_node(type_list<Args...>, const std::size_t index) ENTT_NOEXCEPT;
+
+template<typename Type>
+class ENTT_API meta_node {
+    static_assert(std::is_same_v<Type, std::remove_cv_t<std::remove_reference_t<Type>>>, "Invalid type");
+
+    [[nodiscard]] static auto *meta_default_constructor() ENTT_NOEXCEPT {
+        if constexpr(std::is_default_constructible_v<Type>) {
+            return +[]() { return meta_any{std::in_place_type<Type>}; };
+        } else {
+            return static_cast<std::decay_t<decltype(meta_type_node::default_constructor)>>(nullptr);
+        }
+    }
+
+    [[nodiscard]] static auto *meta_conversion_helper() ENTT_NOEXCEPT {
+        if constexpr(std::is_arithmetic_v<Type>) {
+            return +[](void *bin, const void *value) {
+                return bin ? static_cast<double>(*static_cast<Type *>(bin) = static_cast<Type>(*static_cast<const double *>(value))) : static_cast<double>(*static_cast<const Type *>(value));
+            };
+        } else if constexpr(std::is_enum_v<Type>) {
+            return +[](void *bin, const void *value) {
+                return bin ? static_cast<double>(*static_cast<Type *>(bin) = static_cast<Type>(static_cast<std::underlying_type_t<Type>>(*static_cast<const double *>(value)))) : static_cast<double>(*static_cast<const Type *>(value));
+            };
+        } else {
+            return static_cast<std::decay_t<decltype(meta_type_node::conversion_helper)>>(nullptr);
+        }
+    }
+
+    [[nodiscard]] static meta_template_node *meta_template_info() ENTT_NOEXCEPT {
+        if constexpr(is_complete_v<meta_template_traits<Type>>) {
+            static meta_template_node node{
+                meta_template_traits<Type>::args_type::size,
+                meta_node<typename meta_template_traits<Type>::class_type>::resolve(),
+                [](const std::size_t index) ENTT_NOEXCEPT { return meta_arg_node(typename meta_template_traits<Type>::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<Type>(),
+            {},
+            nullptr,
+            nullptr,
+            size_of_v<Type>,
+            internal::meta_traits::is_none
+                | (std::is_arithmetic_v<Type> ? internal::meta_traits::is_arithmetic : internal::meta_traits::is_none)
+                | (std::is_array_v<Type> ? internal::meta_traits::is_array : internal::meta_traits::is_none)
+                | (std::is_enum_v<Type> ? internal::meta_traits::is_enum : internal::meta_traits::is_none)
+                | (std::is_class_v<Type> ? internal::meta_traits::is_class : internal::meta_traits::is_none)
+                | (std::is_pointer_v<Type> ? internal::meta_traits::is_pointer : internal::meta_traits::is_none)
+                | (is_meta_pointer_like_v<Type> ? internal::meta_traits::is_meta_pointer_like : internal::meta_traits::is_none)
+                | (is_complete_v<meta_sequence_container_traits<Type>> ? internal::meta_traits::is_meta_sequence_container : internal::meta_traits::is_none)
+                | (is_complete_v<meta_associative_container_traits<Type>> ? internal::meta_traits::is_meta_associative_container : internal::meta_traits::is_none),
+            meta_default_constructor(),
+            meta_conversion_helper(),
+            meta_template_info()
+            // tricks clang-format
+        };
+
+        return &node;
+    }
+};
+
+template<typename... Args>
+[[nodiscard]] meta_type_node *meta_arg_node(type_list<Args...>, const std::size_t index) ENTT_NOEXCEPT {
+    meta_type_node *args[sizeof...(Args) + 1u]{nullptr, internal::meta_node<std::remove_const_t<std::remove_reference_t<Args>>>::resolve()...};
+    return args[index + 1u];
+}
+
+template<auto Member, typename Type>
+[[nodiscard]] static std::decay_t<decltype(std::declval<internal::meta_type_node>().*Member)> find_by(const Type &info_or_id, const internal::meta_type_node *node) {
+    for(auto *curr = node->*Member; curr; curr = curr->next) {
+        if constexpr(std::is_same_v<Type, type_info>) {
+            if(*curr->type->info == info_or_id) {
+                return curr;
+            }
+        } else if constexpr(std::is_same_v<decltype(curr), meta_base_node *>) {
+            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<Member>(info_or_id, curr->type); ret) {
+            return ret;
+        }
+    }
+
+    return nullptr;
+}
+
+} // namespace internal
+
+/**
+ * Internal details not to be documented.
+ * @endcond
+ */
+
+} // namespace entt
+
+#endif

+ 48 - 0
Dependencies/include/entt/meta/pointer.hpp

@@ -0,0 +1,48 @@
+#ifndef ENTT_META_POINTER_HPP
+#define ENTT_META_POINTER_HPP
+
+#include <memory>
+#include <type_traits>
+#include "type_traits.hpp"
+
+namespace entt {
+
+/**
+ * @brief Makes plain pointers pointer-like types for the meta system.
+ * @tparam Type Element type.
+ */
+template<typename Type>
+struct is_meta_pointer_like<Type *>
+    : 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<typename Type, std::size_t N>
+struct is_meta_pointer_like<Type (*)[N]>
+    : std::false_type {};
+
+/**
+ * @brief Makes `std::shared_ptr`s of any type pointer-like types for the meta
+ * system.
+ * @tparam Type Element type.
+ */
+template<typename Type>
+struct is_meta_pointer_like<std::shared_ptr<Type>>
+    : 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<typename Type, typename... Args>
+struct is_meta_pointer_like<std::unique_ptr<Type, Args...>>
+    : std::true_type {};
+
+} // namespace entt
+
+#endif

+ 66 - 0
Dependencies/include/entt/meta/policy.hpp

@@ -0,0 +1,66 @@
+#ifndef ENTT_META_POLICY_HPP
+#define ENTT_META_POLICY_HPP
+
+#include <type_traits>
+
+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<typename Type>
+    static constexpr bool value = std::is_reference_v<Type> && !std::is_const_v<std::remove_reference_t<Type>>;
+    /**
+     * 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<typename Type>
+    static constexpr bool value = std::is_reference_v<Type>;
+    /**
+     * 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<typename>
+    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<typename>
+    static constexpr bool value = true;
+    /**
+     * Internal details not to be documented.
+     * @endcond
+     */
+};
+
+} // namespace entt
+
+#endif

+ 124 - 0
Dependencies/include/entt/meta/range.hpp

@@ -0,0 +1,124 @@
+#ifndef ENTT_META_RANGE_HPP
+#define ENTT_META_RANGE_HPP
+
+#include <cstddef>
+#include <iterator>
+#include "../core/iterator.hpp"
+
+namespace entt {
+
+/**
+ * @cond TURN_OFF_DOXYGEN
+ * Internal details not to be documented.
+ */
+
+namespace internal {
+
+template<typename Type, typename Node>
+struct meta_range_iterator {
+    using difference_type = std::ptrdiff_t;
+    using value_type = Type;
+    using pointer = input_iterator_pointer<value_type>;
+    using reference = value_type;
+    using iterator_category = std::input_iterator_tag;
+    using node_type = Node;
+
+    meta_range_iterator() ENTT_NOEXCEPT = default;
+
+    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<typename Type, typename Node = typename Type::node_type>
+struct meta_range {
+    /*! @brief Node type. */
+    using node_type = Node;
+    /*! @brief Input iterator type. */
+    using iterator = internal::meta_range_iterator<Type, Node>;
+    /*! @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)
+        : 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

+ 63 - 0
Dependencies/include/entt/meta/resolve.hpp

@@ -0,0 +1,63 @@
+#ifndef ENTT_META_RESOLVE_HPP
+#define ENTT_META_RESOLVE_HPP
+
+#include <algorithm>
+#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<typename Type>
+[[nodiscard]] meta_type resolve() ENTT_NOEXCEPT {
+    return internal::meta_node<std::remove_const_t<std::remove_reference_t<Type>>>::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<meta_type> resolve() {
+    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

+ 27 - 0
Dependencies/include/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<template<typename...> typename>
+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<template<typename...> typename Clazz, typename... Args>
+struct meta_template_traits<Clazz<Args...>> {
+    /*! @brief Wrapped class template. */
+    using class_type = meta_class_template_tag<Clazz>;
+    /*! @brief List of template arguments. */
+    using args_type = type_list<Args...>;
+};
+
+} // namespace entt
+
+#endif

+ 55 - 0
Dependencies/include/entt/meta/type_traits.hpp

@@ -0,0 +1,55 @@
+#ifndef ENTT_META_TYPE_TRAITS_HPP
+#define ENTT_META_TYPE_TRAITS_HPP
+
+#include <type_traits>
+#include <utility>
+
+namespace entt {
+
+/**
+ * @brief Traits class template to be specialized to enable support for meta
+ * template information.
+ */
+template<typename>
+struct meta_template_traits;
+
+/**
+ * @brief Traits class template to be specialized to enable support for meta
+ * sequence containers.
+ */
+template<typename>
+struct meta_sequence_container_traits;
+
+/**
+ * @brief Traits class template to be specialized to enable support for meta
+ * associative containers.
+ */
+template<typename>
+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<typename>
+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<typename Type>
+struct is_meta_pointer_like<const Type>: is_meta_pointer_like<Type> {};
+
+/**
+ * @brief Helper variable template.
+ * @tparam Type Potentially pointer-like type.
+ */
+template<typename Type>
+inline constexpr auto is_meta_pointer_like_v = is_meta_pointer_like<Type>::value;
+
+} // namespace entt
+
+#endif

+ 396 - 0
Dependencies/include/entt/meta/utility.hpp

@@ -0,0 +1,396 @@
+#ifndef ENTT_META_UTILITY_HPP
+#define ENTT_META_UTILITY_HPP
+
+#include <cstddef>
+#include <functional>
+#include <type_traits>
+#include <utility>
+#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<typename, typename>
+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<typename Type, typename Ret, typename Class, typename... Args>
+struct meta_function_descriptor<Type, Ret (Class::*)(Args...) const> {
+    /*! @brief Meta function return type. */
+    using return_type = Ret;
+    /*! @brief Meta function arguments. */
+    using args_type = std::conditional_t<std::is_base_of_v<Class, Type>, type_list<Args...>, type_list<const Class &, Args...>>;
+
+    /*! @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<Class, Type>;
+};
+
+/**
+ * @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<typename Type, typename Ret, typename Class, typename... Args>
+struct meta_function_descriptor<Type, Ret (Class::*)(Args...)> {
+    /*! @brief Meta function return type. */
+    using return_type = Ret;
+    /*! @brief Meta function arguments. */
+    using args_type = std::conditional_t<std::is_base_of_v<Class, Type>, type_list<Args...>, type_list<Class &, Args...>>;
+
+    /*! @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<Class, Type>;
+};
+
+/**
+ * @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<typename Type, typename Ret, typename Class>
+struct meta_function_descriptor<Type, Ret Class::*> {
+    /*! @brief Meta data return type. */
+    using return_type = Ret &;
+    /*! @brief Meta data arguments. */
+    using args_type = std::conditional_t<std::is_base_of_v<Class, Type>, type_list<>, type_list<Class &>>;
+
+    /*! @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<Class, Type>;
+};
+
+/**
+ * @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<typename Type, typename Ret, typename MaybeType, typename... Args>
+struct meta_function_descriptor<Type, Ret (*)(MaybeType, Args...)> {
+    /*! @brief Meta function return type. */
+    using return_type = Ret;
+    /*! @brief Meta function arguments. */
+    using args_type = std::conditional_t<std::is_base_of_v<std::remove_const_t<std::remove_reference_t<MaybeType>>, Type>, type_list<Args...>, type_list<MaybeType, Args...>>;
+
+    /*! @brief True if the meta function is const, false otherwise. */
+    static constexpr auto is_const = std::is_base_of_v<std::remove_const_t<std::remove_reference_t<MaybeType>>, Type> && std::is_const_v<std::remove_reference_t<MaybeType>>;
+    /*! @brief True if the meta function is static, false otherwise. */
+    static constexpr auto is_static = !std::is_base_of_v<std::remove_const_t<std::remove_reference_t<MaybeType>>, Type>;
+};
+
+/**
+ * @brief Meta function descriptor.
+ * @tparam Type Reflected type to which the meta function is associated.
+ * @tparam Ret Function return type.
+ */
+template<typename Type, typename Ret>
+struct meta_function_descriptor<Type, Ret (*)()> {
+    /*! @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<typename Type, typename Candidate>
+class meta_function_helper {
+    template<typename Ret, typename... Args, typename Class>
+    static constexpr meta_function_descriptor<Type, Ret (Class::*)(Args...) const> get_rid_of_noexcept(Ret (Class::*)(Args...) const);
+
+    template<typename Ret, typename... Args, typename Class>
+    static constexpr meta_function_descriptor<Type, Ret (Class::*)(Args...)> get_rid_of_noexcept(Ret (Class::*)(Args...));
+
+    template<typename Ret, typename Class>
+    static constexpr meta_function_descriptor<Type, Ret Class::*> get_rid_of_noexcept(Ret Class::*);
+
+    template<typename Ret, typename... Args>
+    static constexpr meta_function_descriptor<Type, Ret (*)(Args...)> get_rid_of_noexcept(Ret (*)(Args...));
+
+    template<typename Class>
+    static constexpr meta_function_descriptor<Class, decltype(&Class::operator())> get_rid_of_noexcept(Class);
+
+public:
+    /*! @brief The meta function descriptor of the given function. */
+    using type = decltype(get_rid_of_noexcept(std::declval<Candidate>()));
+};
+
+/**
+ * @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<typename Type, typename Candidate>
+using meta_function_helper_t = typename meta_function_helper<Type, Candidate>::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<typename Policy = as_is_t, typename Type>
+meta_any meta_dispatch([[maybe_unused]] Type &&value) {
+    if constexpr(std::is_same_v<Policy, as_void_t>) {
+        return meta_any{std::in_place_type<void>};
+    } else if constexpr(std::is_same_v<Policy, as_ref_t>) {
+        return meta_any{std::in_place_type<Type>, std::forward<Type>(value)};
+    } else if constexpr(std::is_same_v<Policy, as_cref_t>) {
+        static_assert(std::is_lvalue_reference_v<Type>, "Invalid type");
+        return meta_any{std::in_place_type<const std::remove_reference_t<Type> &>, std::as_const(value)};
+    } else {
+        static_assert(std::is_same_v<Policy, as_is_t>, "Policy not supported");
+        return meta_any{std::forward<Type>(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<typename Type>
+[[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<typename Type, auto Data>
+[[nodiscard]] bool meta_setter([[maybe_unused]] meta_handle instance, [[maybe_unused]] meta_any value) {
+    if constexpr(!std::is_same_v<decltype(Data), Type> && !std::is_same_v<decltype(Data), std::nullptr_t>) {
+        if constexpr(std::is_member_function_pointer_v<decltype(Data)> || std::is_function_v<std::remove_reference_t<std::remove_pointer_t<decltype(Data)>>>) {
+            using descriptor = meta_function_helper_t<Type, decltype(Data)>;
+            using data_type = type_list_element_t<descriptor::is_static, typename descriptor::args_type>;
+
+            if(auto *const clazz = instance->try_cast<Type>(); clazz && value.allow_cast<data_type>()) {
+                std::invoke(Data, *clazz, value.cast<data_type>());
+                return true;
+            }
+        } else if constexpr(std::is_member_object_pointer_v<decltype(Data)>) {
+            using data_type = std::remove_reference_t<typename meta_function_helper_t<Type, decltype(Data)>::return_type>;
+
+            if constexpr(!std::is_array_v<data_type> && !std::is_const_v<data_type>) {
+                if(auto *const clazz = instance->try_cast<Type>(); clazz && value.allow_cast<data_type>()) {
+                    std::invoke(Data, *clazz) = value.cast<data_type>();
+                    return true;
+                }
+            }
+        } else {
+            using data_type = std::remove_reference_t<decltype(*Data)>;
+
+            if constexpr(!std::is_array_v<data_type> && !std::is_const_v<data_type>) {
+                if(value.allow_cast<data_type>()) {
+                    *Data = value.cast<data_type>();
+                    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<typename Type, auto Data, typename Policy = as_is_t>
+[[nodiscard]] meta_any meta_getter([[maybe_unused]] meta_handle instance) {
+    if constexpr(std::is_member_pointer_v<decltype(Data)> || std::is_function_v<std::remove_reference_t<std::remove_pointer_t<decltype(Data)>>>) {
+        if constexpr(!std::is_array_v<std::remove_cv_t<std::remove_reference_t<std::invoke_result_t<decltype(Data), Type &>>>>) {
+            if constexpr(std::is_invocable_v<decltype(Data), Type &>) {
+                if(auto *clazz = instance->try_cast<Type>(); clazz) {
+                    return meta_dispatch<Policy>(std::invoke(Data, *clazz));
+                }
+            }
+
+            if constexpr(std::is_invocable_v<decltype(Data), const Type &>) {
+                if(auto *fallback = instance->try_cast<const Type>(); fallback) {
+                    return meta_dispatch<Policy>(std::invoke(Data, *fallback));
+                }
+            }
+        }
+
+        return meta_any{};
+    } else if constexpr(std::is_pointer_v<decltype(Data)>) {
+        if constexpr(std::is_array_v<std::remove_pointer_t<decltype(Data)>>) {
+            return meta_any{};
+        } else {
+            return meta_dispatch<Policy>(*Data);
+        }
+    } else {
+        return meta_dispatch<Policy>(Data);
+    }
+}
+
+/**
+ * @cond TURN_OFF_DOXYGEN
+ * Internal details not to be documented.
+ */
+
+namespace internal {
+
+template<typename Type, typename Policy, typename Candidate, typename... Args>
+[[nodiscard]] meta_any meta_invoke_with_args(Candidate &&candidate, Args &&...args) {
+    if constexpr(std::is_same_v<std::invoke_result_t<decltype(candidate), Args...>, void>) {
+        std::invoke(candidate, args...);
+        return meta_any{std::in_place_type<void>};
+    } else {
+        return meta_dispatch<Policy>(std::invoke(candidate, args...));
+    }
+}
+
+template<typename Type, typename Policy, typename Candidate, std::size_t... Index>
+[[nodiscard]] meta_any meta_invoke([[maybe_unused]] meta_handle instance, Candidate &&candidate, [[maybe_unused]] meta_any *args, std::index_sequence<Index...>) {
+    using descriptor = meta_function_helper_t<Type, std::remove_reference_t<Candidate>>;
+
+    if constexpr(std::is_invocable_v<std::remove_reference_t<Candidate>, const Type &, type_list_element_t<Index, typename descriptor::args_type>...>) {
+        if(const auto *const clazz = instance->try_cast<const Type>(); clazz && ((args + Index)->allow_cast<type_list_element_t<Index, typename descriptor::args_type>>() && ...)) {
+            return meta_invoke_with_args<Type, Policy>(std::forward<Candidate>(candidate), *clazz, (args + Index)->cast<type_list_element_t<Index, typename descriptor::args_type>>()...);
+        }
+    } else if constexpr(std::is_invocable_v<std::remove_reference_t<Candidate>, Type &, type_list_element_t<Index, typename descriptor::args_type>...>) {
+        if(auto *const clazz = instance->try_cast<Type>(); clazz && ((args + Index)->allow_cast<type_list_element_t<Index, typename descriptor::args_type>>() && ...)) {
+            return meta_invoke_with_args<Type, Policy>(std::forward<Candidate>(candidate), *clazz, (args + Index)->cast<type_list_element_t<Index, typename descriptor::args_type>>()...);
+        }
+    } else {
+        if(((args + Index)->allow_cast<type_list_element_t<Index, typename descriptor::args_type>>() && ...)) {
+            return meta_invoke_with_args<Type, Policy>(std::forward<Candidate>(candidate), (args + Index)->cast<type_list_element_t<Index, typename descriptor::args_type>>()...);
+        }
+    }
+
+    return meta_any{};
+}
+
+template<typename Type, typename... Args, std::size_t... Index>
+[[nodiscard]] meta_any meta_construct(meta_any *const args, std::index_sequence<Index...>) {
+    if(((args + Index)->allow_cast<Args>() && ...)) {
+        return meta_any{std::in_place_type<Type>, (args + Index)->cast<Args>()...};
+    }
+
+    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<typename Type, typename Policy = as_is_t, typename Candidate>
+[[nodiscard]] meta_any meta_invoke([[maybe_unused]] meta_handle instance, Candidate &&candidate, [[maybe_unused]] meta_any *const args) {
+    return internal::meta_invoke<Type, Policy>(std::move(instance), std::forward<Candidate>(candidate), args, std::make_index_sequence<meta_function_helper_t<Type, std::remove_reference_t<Candidate>>::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<typename Type, auto Candidate, typename Policy = as_is_t>
+[[nodiscard]] meta_any meta_invoke(meta_handle instance, meta_any *const args) {
+    return internal::meta_invoke<Type, Policy>(std::move(instance), Candidate, args, std::make_index_sequence<meta_function_helper_t<Type, std::remove_reference_t<decltype(Candidate)>>::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<typename Type, typename... Args>
+[[nodiscard]] meta_any meta_construct(meta_any *const args) {
+    return internal::meta_construct<Type, Args...>(args, std::index_sequence_for<Args...>{});
+}
+
+/**
+ * @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<typename Type, typename Policy = as_is_t, typename Candidate>
+[[nodiscard]] meta_any meta_construct(Candidate &&candidate, meta_any *const args) {
+    if constexpr(meta_function_helper_t<Type, Candidate>::is_static) {
+        return internal::meta_invoke<Type, Policy>({}, std::forward<Candidate>(candidate), args, std::make_index_sequence<meta_function_helper_t<Type, std::remove_reference_t<Candidate>>::args_type::size>{});
+    } else {
+        return internal::meta_invoke<Type, Policy>(*args, std::forward<Candidate>(candidate), args + 1u, std::make_index_sequence<meta_function_helper_t<Type, std::remove_reference_t<Candidate>>::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<typename Type, auto Candidate, typename Policy = as_is_t>
+[[nodiscard]] meta_any meta_construct(meta_any *const args) {
+    return meta_construct<Type, Policy>(Candidate, args);
+}
+
+} // namespace entt
+
+#endif

+ 67 - 0
Dependencies/include/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 <android/ndk-version.h>
+#    if __NDK_MAJOR__ == 17
+
+#        include <functional>
+#        include <type_traits>
+#        include <utility>
+
+namespace std {
+
+namespace internal {
+
+template<typename Func, typename... Args>
+constexpr auto is_invocable(int) -> decltype(std::invoke(std::declval<Func>(), std::declval<Args>()...), std::true_type{});
+
+template<typename, typename...>
+constexpr std::false_type is_invocable(...);
+
+template<typename Ret, typename Func, typename... Args>
+constexpr auto is_invocable_r(int)
+-> std::enable_if_t<decltype(std::is_convertible_v<decltype(std::invoke(std::declval<Func>(), std::declval<Args>()...)), Ret>, std::true_type>;
+
+
+template<typename, typename, typename...>
+constexpr std::false_type is_invocable_r(...);
+
+} // namespace internal
+
+template<typename Func, typename... Args>
+struct is_invocable: decltype(internal::is_invocable<Func, Args...>(0)) {};
+
+template<typename Func, typename... Argsv>
+inline constexpr bool is_invocable_v = std::is_invocable<Func, Args...>::value;
+
+template<typename Ret, typename Func, typename... Args>
+struct is_invocable_r: decltype(internal::is_invocable_r<Ret, Func, Args...>(0)) {};
+
+template<typename Ret, typename Func, typename... Args>
+inline constexpr bool is_invocable_r_v = std::is_invocable_r<Ret, Func, Args...>::value;
+
+template<typename Func, typename... Args>
+struct invoke_result {
+    using type = decltype(std::invoke(std::declval<Func>(), std::declval<Args>()...));
+};
+
+template<typename Func, typename... Args>
+using invoke_result_t = typename std::invoke_result<Func, Args...>::type;
+
+} // namespace std
+
+#    endif
+#endif
+
+/**
+ * Internal details not to be documented.
+ * @endcond
+ */
+
+#endif

+ 20 - 0
Dependencies/include/entt/poly/fwd.hpp

@@ -0,0 +1,20 @@
+#ifndef ENTT_POLY_FWD_HPP
+#define ENTT_POLY_FWD_HPP
+
+#include <type_traits>
+
+namespace entt {
+
+template<typename, std::size_t Len, std::size_t = alignof(typename std::aligned_storage_t<Len + !Len>)>
+class basic_poly;
+
+/**
+ * @brief Alias declaration for the most common use case.
+ * @tparam Concept Concept descriptor.
+ */
+template<typename Concept>
+using poly = basic_poly<Concept, sizeof(double[2])>;
+
+} // namespace entt
+
+#endif

+ 313 - 0
Dependencies/include/entt/poly/poly.hpp

@@ -0,0 +1,313 @@
+#ifndef ENTT_POLY_POLY_HPP
+#define ENTT_POLY_POLY_HPP
+
+#include <cstddef>
+#include <functional>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#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<class Type>
+    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<auto Member, typename... Args>
+    poly_inspector invoke(Args &&...args) const;
+
+    /*! @copydoc invoke */
+    template<auto Member, typename... Args>
+    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<typename Concept, std::size_t Len, std::size_t Align>
+class poly_vtable {
+    using inspector = typename Concept::template type<poly_inspector>;
+
+    template<typename Ret, typename... Args>
+    static auto vtable_entry(Ret (*)(inspector &, Args...)) -> Ret (*)(basic_any<Len, Align> &, Args...);
+
+    template<typename Ret, typename... Args>
+    static auto vtable_entry(Ret (*)(const inspector &, Args...)) -> Ret (*)(const basic_any<Len, Align> &, Args...);
+
+    template<typename Ret, typename... Args>
+    static auto vtable_entry(Ret (*)(Args...)) -> Ret (*)(const basic_any<Len, Align> &, Args...);
+
+    template<typename Ret, typename... Args>
+    static auto vtable_entry(Ret (inspector::*)(Args...)) -> Ret (*)(basic_any<Len, Align> &, Args...);
+
+    template<typename Ret, typename... Args>
+    static auto vtable_entry(Ret (inspector::*)(Args...) const) -> Ret (*)(const basic_any<Len, Align> &, Args...);
+
+    template<auto... Candidate>
+    static auto make_vtable(value_list<Candidate...>)
+        -> decltype(std::make_tuple(vtable_entry(Candidate)...));
+
+    template<typename... Func>
+    [[nodiscard]] static constexpr auto make_vtable(type_list<Func...>) {
+        if constexpr(sizeof...(Func) == 0u) {
+            return decltype(make_vtable(typename Concept::template impl<inspector>{})){};
+        } else if constexpr((std::is_function_v<Func> && ...)) {
+            return decltype(std::make_tuple(vtable_entry(std::declval<Func inspector::*>())...)){};
+        }
+    }
+
+    template<typename Type, auto Candidate, typename Ret, typename Any, typename... Args>
+    static void fill_vtable_entry(Ret (*&entry)(Any &, Args...)) {
+        if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Args...>) {
+            entry = +[](Any &, Args... args) -> Ret {
+                return std::invoke(Candidate, std::forward<Args>(args)...);
+            };
+        } else {
+            entry = +[](Any &instance, Args... args) -> Ret {
+                return static_cast<Ret>(std::invoke(Candidate, any_cast<constness_as_t<Type, Any> &>(instance), std::forward<Args>(args)...));
+            };
+        }
+    }
+
+    template<typename Type, auto... Index>
+    [[nodiscard]] static auto fill_vtable(std::index_sequence<Index...>) {
+        vtable_type impl{};
+        (fill_vtable_entry<Type, value_list_element_v<Index, typename Concept::template impl<Type>>>(std::get<Index>(impl)), ...);
+        return impl;
+    }
+
+    using vtable_type = decltype(make_vtable(Concept{}));
+    static constexpr bool is_mono_v = std::tuple_size_v<vtable_type> == 1u;
+
+public:
+    /*! @brief Virtual table type. */
+    using type = std::conditional_t<is_mono_v, std::tuple_element_t<0u, vtable_type>, 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<typename Type>
+    [[nodiscard]] static type instance() {
+        static_assert(std::is_same_v<Type, std::decay_t<Type>>, "Type differs from its decayed form");
+        static const vtable_type vtable = fill_vtable<Type>(std::make_index_sequence<Concept::template impl<Type>::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<typename Poly>
+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<auto Member, typename... Args>
+    [[nodiscard]] decltype(auto) invoke(const poly_base &self, Args &&...args) const {
+        const auto &poly = static_cast<const Poly &>(self);
+
+        if constexpr(std::is_function_v<std::remove_pointer_t<decltype(poly.vtable)>>) {
+            return poly.vtable(poly.storage, std::forward<Args>(args)...);
+        } else {
+            return std::get<Member>(*poly.vtable)(poly.storage, std::forward<Args>(args)...);
+        }
+    }
+
+    /*! @copydoc invoke */
+    template<auto Member, typename... Args>
+    [[nodiscard]] decltype(auto) invoke(poly_base &self, Args &&...args) {
+        auto &poly = static_cast<Poly &>(self);
+
+        if constexpr(std::is_function_v<std::remove_pointer_t<decltype(poly.vtable)>>) {
+            return poly.vtable(poly.storage, std::forward<Args>(args)...);
+        } else {
+            return std::get<Member>(*poly.vtable)(poly.storage, std::forward<Args>(args)...);
+        }
+    }
+};
+
+/**
+ * @brief Shortcut for calling `poly_base<Type>::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<auto Member, typename Poly, typename... Args>
+decltype(auto) poly_call(Poly &&self, Args &&...args) {
+    return std::forward<Poly>(self).template invoke<Member>(self, std::forward<Args>(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.<br/>
+ * This class aims to make it simple and easy to use.
+ *
+ * @note
+ * Both deduced and defined static virtual tables are supported.<br/>
+ * 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<typename Concept, std::size_t Len, std::size_t Align>
+class basic_poly: private Concept::template type<poly_base<basic_poly<Concept, Len, Align>>> {
+    /*! @brief A poly base is allowed to snoop into a poly object. */
+    friend struct poly_base<basic_poly>;
+
+public:
+    /*! @brief Concept type. */
+    using concept_type = typename Concept::template type<poly_base<basic_poly>>;
+    /*! @brief Virtual table type. */
+    using vtable_type = typename poly_vtable<Concept, Len, Align>::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<typename Type, typename... Args>
+    explicit basic_poly(std::in_place_type_t<Type>, Args &&...args)
+        : storage{std::in_place_type<Type>, std::forward<Args>(args)...},
+          vtable{poly_vtable<Concept, Len, Align>::template instance<std::remove_const_t<std::remove_reference_t<Type>>>()} {}
+
+    /**
+     * @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<typename Type, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Type>>, basic_poly>>>
+    basic_poly(Type &&value) ENTT_NOEXCEPT
+        : basic_poly{std::in_place_type<std::remove_cv_t<std::remove_reference_t<Type>>>, std::forward<Type>(value)} {}
+
+    /**
+     * @brief Returns the object type if any, `type_id<void>()` otherwise.
+     * @return The object type if any, `type_id<void>()` 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<typename Type, typename... Args>
+    void emplace(Args &&...args) {
+        storage.template emplace<Type>(std::forward<Args>(args)...);
+        vtable = poly_vtable<Concept, Len, Align>::template instance<std::remove_const_t<std::remove_reference_t<Type>>>();
+    }
+
+    /*! @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<bool>(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<Len, Align> storage;
+    vtable_type vtable;
+};
+
+} // namespace entt
+
+#endif

+ 332 - 0
Dependencies/include/entt/process/process.hpp

@@ -0,0 +1,332 @@
+#ifndef ENTT_PROCESS_PROCESS_HPP
+#define ENTT_PROCESS_PROCESS_HPP
+
+#include <cstdint>
+#include <type_traits>
+#include <utility>
+#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.<br/>
+ * 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<typename Derived, typename Delta>
+class process {
+    enum class state : std::uint8_t {
+        uninitialized = 0,
+        running,
+        paused,
+        succeeded,
+        failed,
+        aborted,
+        finished,
+        rejected
+    };
+
+    template<typename Target = Derived>
+    auto next(std::integral_constant<state, state::uninitialized>)
+        -> decltype(std::declval<Target>().init(), void()) {
+        static_cast<Target *>(this)->init();
+    }
+
+    template<typename Target = Derived>
+    auto next(std::integral_constant<state, state::running>, Delta delta, void *data)
+        -> decltype(std::declval<Target>().update(delta, data), void()) {
+        static_cast<Target *>(this)->update(delta, data);
+    }
+
+    template<typename Target = Derived>
+    auto next(std::integral_constant<state, state::succeeded>)
+        -> decltype(std::declval<Target>().succeeded(), void()) {
+        static_cast<Target *>(this)->succeeded();
+    }
+
+    template<typename Target = Derived>
+    auto next(std::integral_constant<state, state::failed>)
+        -> decltype(std::declval<Target>().failed(), void()) {
+        static_cast<Target *>(this)->failed();
+    }
+
+    template<typename Target = Derived>
+    auto next(std::integral_constant<state, state::aborted>)
+        -> decltype(std::declval<Target>().aborted(), void()) {
+        static_cast<Target *>(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() {
+        static_assert(std::is_base_of_v<process, Derived>, "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<state, state::uninitialized>{});
+            current = state::running;
+            break;
+        case state::running:
+            next(std::integral_constant<state, state::running>{}, 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<state, state::succeeded>{});
+            current = state::finished;
+            break;
+        case state::failed:
+            next(std::integral_constant<state, state::failed>{});
+            current = state::rejected;
+            break;
+        case state::aborted:
+            next(std::integral_constant<state, state::aborted>{});
+            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.<br/>
+ * 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<typename Func, typename Delta>
+struct process_adaptor: process<process_adaptor<Func, Delta>, 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<typename... Args>
+    process_adaptor(Args &&...args)
+        : Func{std::forward<Args>(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

+ 290 - 0
Dependencies/include/entt/process/scheduler.hpp

@@ -0,0 +1,290 @@
+#ifndef ENTT_PROCESS_SCHEDULER_HPP
+#define ENTT_PROCESS_SCHEDULER_HPP
+
+#include <algorithm>
+#include <memory>
+#include <type_traits>
+#include <utility>
+#include <vector>
+#include "../config/config.h"
+#include "process.hpp"
+
+namespace entt {
+
+/**
+ * @brief Cooperative scheduler for processes.
+ *
+ * A cooperative scheduler runs processes and helps managing their life cycles.
+ *
+ * Each process is invoked once per tick. If a process terminates, it's
+ * removed automatically from the scheduler and it's never invoked again.<br/>
+ * 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<my_process>(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<typename Delta>
+class scheduler {
+    struct process_handler {
+        using instance_type = std::unique_ptr<void, void (*)(void *)>;
+        using update_fn_type = bool(process_handler &, Delta, void *);
+        using abort_fn_type = void(process_handler &, bool);
+        using next_type = std::unique_ptr<process_handler>;
+
+        instance_type instance;
+        update_fn_type *update;
+        abort_fn_type *abort;
+        next_type next;
+    };
+
+    struct continuation {
+        continuation(process_handler *ref)
+            : handler{ref} {}
+
+        template<typename Proc, typename... Args>
+        continuation then(Args &&...args) {
+            static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type");
+            auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &scheduler::deleter<Proc>};
+            handler->next.reset(new process_handler{std::move(proc), &scheduler::update<Proc>, &scheduler::abort<Proc>, nullptr});
+            handler = handler->next.get();
+            return *this;
+        }
+
+        template<typename Func>
+        continuation then(Func &&func) {
+            return then<process_adaptor<std::decay_t<Func>, Delta>>(std::forward<Func>(func));
+        }
+
+    private:
+        process_handler *handler;
+    };
+
+    template<typename Proc>
+    [[nodiscard]] static bool update(process_handler &handler, const Delta delta, void *data) {
+        auto *process = static_cast<Proc *>(handler.instance.get());
+        process->tick(delta, data);
+
+        if(process->rejected()) {
+            return true;
+        } else if(process->finished()) {
+            if(handler.next) {
+                handler = std::move(*handler.next);
+                // forces the process to exit the uninitialized state
+                return handler.update(handler, {}, nullptr);
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    template<typename Proc>
+    static void abort(process_handler &handler, const bool immediately) {
+        static_cast<Proc *>(handler.instance.get())->abort(immediately);
+    }
+
+    template<typename Proc>
+    static void deleter(void *proc) {
+        delete static_cast<Proc *>(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<my_process>(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<my_other_process>();
+     * @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<typename Proc, typename... Args>
+    auto attach(Args &&...args) {
+        static_assert(std::is_base_of_v<process<Proc, Delta>, Proc>, "Invalid process type");
+        auto proc = typename process_handler::instance_type{new Proc{std::forward<Args>(args)...}, &scheduler::deleter<Proc>};
+        process_handler handler{std::move(proc), &scheduler::update<Proc>, &scheduler::abort<Proc>, nullptr};
+        // forces the process to exit the uninitialized state
+        handler.update(handler, {}, nullptr);
+        return continuation{&handlers.emplace_back(std::move(handler))};
+    }
+
+    /**
+     * @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.<br/>
+     * 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<my_process>(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.
+     */
+    template<typename Func>
+    auto attach(Func &&func) {
+        using Proc = process_adaptor<std::decay_t<Func>, Delta>;
+        return attach<Proc>(std::forward<Func>(func));
+    }
+
+    /**
+     * @brief Updates all scheduled processes.
+     *
+     * All scheduled processes are executed in no specific order.<br/>
+     * 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) {
+        auto sz = handlers.size();
+
+        for(auto pos = handlers.size(); pos; --pos) {
+            auto &handler = handlers[pos - 1];
+
+            if(const auto dead = handler.update(handler, delta, data); dead) {
+                std::swap(handler, handlers[--sz]);
+            }
+        }
+
+        handlers.erase(handlers.begin() + sz, handlers.end());
+    }
+
+    /**
+     * @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.<br/>
+     * 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) {
+        decltype(handlers) exec;
+        exec.swap(handlers);
+
+        for(auto &&handler: exec) {
+            handler.abort(handler, immediately);
+        }
+
+        std::move(handlers.begin(), handlers.end(), std::back_inserter(exec));
+        handlers.swap(exec);
+    }
+
+private:
+    std::vector<process_handler> handlers{};
+};
+
+} // namespace entt
+
+#endif

+ 280 - 0
Dependencies/include/entt/resource/cache.hpp

@@ -0,0 +1,280 @@
+#ifndef ENTT_RESOURCE_CACHE_HPP
+#define ENTT_RESOURCE_CACHE_HPP
+
+#include <type_traits>
+#include <utility>
+#include "../config/config.h"
+#include "../container/dense_hash_map.hpp"
+#include "../core/fwd.hpp"
+#include "../core/utility.hpp"
+#include "fwd.hpp"
+#include "handle.hpp"
+#include "loader.hpp"
+
+namespace entt {
+
+/**
+ * @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.
+ */
+template<typename Resource>
+class resource_cache {
+    static_assert(std::is_same_v<Resource, std::remove_const_t<std::remove_reference_t<Resource>>>, "Invalid resource type");
+
+public:
+    /*! @brief Unsigned integer type. */
+    using size_type = std::size_t;
+    /*! @brief Type of resources managed by a cache. */
+    using resource_type = Resource;
+
+    /*! @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 Number of resources managed by a cache.
+     * @return Number of resources currently stored.
+     */
+    [[nodiscard]] size_type size() const ENTT_NOEXCEPT {
+        return resources.size();
+    }
+
+    /**
+     * @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 resources.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.
+     */
+    void clear() ENTT_NOEXCEPT {
+        resources.clear();
+    }
+
+    /**
+     * @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<typename Loader, typename... Args>
+    resource_handle<resource_type> load(const id_type id, Args &&...args) {
+        if(auto it = resources.find(id); it == resources.cend()) {
+            if(auto handle = temp<Loader>(std::forward<Args>(args)...); handle) {
+                return (resources[id] = std::move(handle));
+            }
+        } else {
+            return it->second;
+        }
+
+        return {};
+    }
+
+    /**
+     * @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.
+     */
+    template<typename Loader, typename... Args>
+    resource_handle<resource_type> reload(const id_type id, Args &&...args) {
+        return (discard(id), load<Loader>(id, std::forward<Args>(args)...));
+    }
+
+    /**
+     * @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.
+     */
+    template<typename Loader, typename... Args>
+    [[nodiscard]] resource_handle<resource_type> temp(Args &&...args) const {
+        return Loader{}.get(std::forward<Args>(args)...);
+    }
+
+    /**
+     * @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.
+     */
+    [[nodiscard]] resource_handle<const resource_type> handle(const id_type id) const {
+        if(auto it = resources.find(id); it != resources.cend()) {
+            return it->second;
+        }
+
+        return {};
+    }
+
+    /*! @copydoc handle */
+    [[nodiscard]] resource_handle<resource_type> handle(const id_type id) {
+        if(auto it = resources.find(id); it != resources.end()) {
+            return it->second;
+        }
+
+        return {};
+    }
+
+    /**
+     * @brief Checks if a cache contains a given identifier.
+     * @param id Unique resource identifier.
+     * @return True if the cache contains the resource, false otherwise.
+     */
+    [[nodiscard]] bool contains(const id_type id) const {
+        return (resources.find(id) != resources.cend());
+    }
+
+    /**
+     * @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.
+     */
+    void discard(const id_type id) {
+        if(auto it = resources.find(id); it != resources.end()) {
+            resources.erase(it);
+        }
+    }
+
+    /**
+     * @brief Iterates all resources.
+     *
+     * The function object is invoked for each element. It is provided with
+     * either the resource identifier, the resource handle or both of them.<br/>
+     * The signature of the function must be equivalent to one of the following
+     * forms:
+     *
+     * @code{.cpp}
+     * void(const entt::id_type);
+     * void(entt::resource_handle<const resource_type>);
+     * void(const entt::id_type, entt::resource_handle<const resource_type>);
+     * @endcode
+     *
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename Func>
+    void each(Func func) const {
+        auto begin = resources.begin();
+        auto end = resources.end();
+
+        while(begin != end) {
+            auto curr = begin++;
+
+            if constexpr(std::is_invocable_v<Func, id_type>) {
+                func(curr->first);
+            } else if constexpr(std::is_invocable_v<Func, resource_handle<const resource_type>>) {
+                func(resource_handle<const resource_type>{curr->second});
+            } else {
+                func(curr->first, resource_handle<const resource_type>{curr->second});
+            }
+        }
+    }
+
+    /**
+     * @copybrief each
+     *
+     * The function object is invoked for each element. It is provided with
+     * either the resource identifier, the resource handle or both of them.<br/>
+     * The signature of the function must be equivalent to one of the following
+     * forms:
+     *
+     * @code{.cpp}
+     * void(const entt::id_type);
+     * void(entt::resource_handle<resource_type>);
+     * void(const entt::id_type, entt::resource_handle<resource_type>);
+     * @endcode
+     *
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename Func>
+    void each(Func func) {
+        auto begin = resources.begin();
+        auto end = resources.end();
+
+        while(begin != end) {
+            auto curr = begin++;
+
+            if constexpr(std::is_invocable_v<Func, id_type>) {
+                func(curr->first);
+            } else if constexpr(std::is_invocable_v<Func, resource_handle<resource_type>>) {
+                func(curr->second);
+            } else {
+                func(curr->first, curr->second);
+            }
+        }
+    }
+
+private:
+    dense_hash_map<id_type, resource_handle<resource_type>, identity> resources;
+};
+
+} // namespace entt
+
+#endif

+ 17 - 0
Dependencies/include/entt/resource/fwd.hpp

@@ -0,0 +1,17 @@
+#ifndef ENTT_RESOURCE_FWD_HPP
+#define ENTT_RESOURCE_FWD_HPP
+
+namespace entt {
+
+template<typename>
+class resource_cache;
+
+template<typename>
+class resource_handle;
+
+template<typename, typename>
+class resource_loader;
+
+} // namespace entt
+
+#endif

+ 183 - 0
Dependencies/include/entt/resource/handle.hpp

@@ -0,0 +1,183 @@
+#ifndef ENTT_RESOURCE_HANDLE_HPP
+#define ENTT_RESOURCE_HANDLE_HPP
+
+#include <memory>
+#include <type_traits>
+#include <utility>
+#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.<br/>
+ * 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<typename Resource>
+class resource_handle {
+    /*! @brief Resource handles are friends with each other. */
+    template<typename>
+    friend class resource_handle;
+
+public:
+    /*! @brief Unsigned integer type. */
+    using size_type = long;
+    /*! @brief Type of resources managed by a cache. */
+    using resource_type = Resource;
+
+    /*! @brief Default constructor. */
+    resource_handle() ENTT_NOEXCEPT = default;
+
+    /**
+     * @brief Creates a handle from a shared pointer, namely a resource.
+     * @param res A pointer to a properly initialized resource.
+     */
+    resource_handle(std::shared_ptr<resource_type> res) ENTT_NOEXCEPT
+        : resource{std::move(res)} {}
+
+    /**
+     * @brief Copy constructor.
+     * @param other The instance to copy from.
+     */
+    resource_handle(const resource_handle &other) ENTT_NOEXCEPT = default;
+
+    /**
+     * @brief Move constructor.
+     * @param other The instance to move from.
+     */
+    resource_handle(resource_handle &&other) 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<typename Other>
+    resource_handle(const resource_handle<Other> &other, resource_type &res) noexcept
+        : resource{other.resource, 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<typename Other, typename = std::enable_if_t<!std::is_same_v<resource_type, Other> && std::is_base_of_v<resource_type, Other>>>
+    resource_handle(const resource_handle<Other> &other) ENTT_NOEXCEPT
+        : resource{other.resource} {}
+
+    /**
+     * @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<typename Other, typename = std::enable_if_t<!std::is_same_v<resource_type, Other> && std::is_base_of_v<resource_type, Other>>>
+    resource_handle(resource_handle<Other> &&other) ENTT_NOEXCEPT
+        : resource{std::move(other.resource)} {}
+
+    /**
+     * @brief Copy assignment operator.
+     * @param other The instance to copy from.
+     * @return This resource handle.
+     */
+    resource_handle &operator=(const resource_handle &other) ENTT_NOEXCEPT = default;
+
+    /**
+     * @brief Move assignment operator.
+     * @param other The instance to move from.
+     * @return This resource handle.
+     */
+    resource_handle &operator=(resource_handle &&other) 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<typename Other>
+    std::enable_if_t<!std::is_same_v<resource_type, Other> && std::is_base_of_v<resource_type, Other>, resource_handle &>
+    operator=(const resource_handle<Other> &other) ENTT_NOEXCEPT {
+        resource = other.resource;
+        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<typename Other>
+    std::enable_if_t<!std::is_same_v<resource_type, Other> && std::is_base_of_v<resource_type, Other>, resource_handle &>
+    operator=(resource_handle<Other> &&other) ENTT_NOEXCEPT {
+        resource = std::move(other.resource);
+        return *this;
+    }
+
+    /**
+     * @brief Gets 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]] resource_type &get() const ENTT_NOEXCEPT {
+        return *resource;
+    }
+
+    /*! @copydoc get */
+    [[nodiscard]] operator resource_type &() const ENTT_NOEXCEPT {
+        return get();
+    }
+
+    /*! @copydoc get */
+    [[nodiscard]] resource_type &operator*() const 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.
+     *
+     * @return A pointer to the managed resource or `nullptr` if the handle
+     * contains no resource at all.
+     */
+    [[nodiscard]] resource_type *operator->() const ENTT_NOEXCEPT {
+        return resource.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<bool>(resource);
+    }
+
+    /**
+     * @brief Returns the number of handles pointing the same resource.
+     * @return The number of handles pointing the same resource.
+     */
+    [[nodiscard]] size_type use_count() const ENTT_NOEXCEPT {
+        return resource.use_count();
+    }
+
+private:
+    std::shared_ptr<resource_type> resource;
+};
+
+} // namespace entt
+
+#endif

+ 61 - 0
Dependencies/include/entt/resource/loader.hpp

@@ -0,0 +1,61 @@
+#ifndef ENTT_RESOURCE_LOADER_HPP
+#define ENTT_RESOURCE_LOADER_HPP
+
+#include "fwd.hpp"
+#include "handle.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 handle to the resource just created.<br/>
+ * As an example:
+ *
+ * @code{.cpp}
+ * struct my_resource {};
+ *
+ * struct my_loader: entt::resource_loader<my_loader, my_resource> {
+ *     resource_handle<my_resource> load(int value) const {
+ *         // use the integer value somehow
+ *         return std::make_shared<my_resource>();
+ *     }
+ * };
+ * @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.
+ */
+template<typename Loader, typename Resource>
+class resource_loader {
+    /*! @brief Resource loaders are friends of their caches. */
+    template<typename Other>
+    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<typename... Args>
+    [[nodiscard]] resource_handle<Resource> get(Args &&...args) const {
+        return static_cast<const Loader *>(this)->load(std::forward<Args>(args)...);
+    }
+};
+
+} // namespace entt
+
+#endif

+ 339 - 0
Dependencies/include/entt/signal/delegate.hpp

@@ -0,0 +1,339 @@
+#ifndef ENTT_SIGNAL_DELEGATE_HPP
+#define ENTT_SIGNAL_DELEGATE_HPP
+
+#include <cstddef>
+#include <functional>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include "../config/config.h"
+#include "../core/type_traits.hpp"
+
+namespace entt {
+
+/**
+ * @cond TURN_OFF_DOXYGEN
+ * Internal details not to be documented.
+ */
+
+namespace internal {
+
+template<typename Ret, typename... Args>
+auto function_pointer(Ret (*)(Args...)) -> Ret (*)(Args...);
+
+template<typename Ret, typename Type, typename... Args, typename Other>
+auto function_pointer(Ret (*)(Type, Args...), Other &&) -> Ret (*)(Args...);
+
+template<typename Class, typename Ret, typename... Args, typename... Other>
+auto function_pointer(Ret (Class::*)(Args...), Other &&...) -> Ret (*)(Args...);
+
+template<typename Class, typename Ret, typename... Args, typename... Other>
+auto function_pointer(Ret (Class::*)(Args...) const, Other &&...) -> Ret (*)(Args...);
+
+template<typename Class, typename Type, typename... Other>
+auto function_pointer(Type Class::*, Other &&...) -> Type (*)();
+
+template<typename... Type>
+using function_pointer_t = decltype(internal::function_pointer(std::declval<Type>()...));
+
+template<typename... Class, typename Ret, typename... Args>
+[[nodiscard]] constexpr auto index_sequence_for(Ret (*)(Args...)) {
+    return std::index_sequence_for<Class..., Args...>{};
+}
+
+} // namespace internal
+
+/**
+ * Internal details not to be documented.
+ * @endcond
+ */
+
+/*! @brief Used to wrap a function or a member of a specified type. */
+template<auto>
+struct connect_arg_t {};
+
+/*! @brief Constant of type connect_arg_t used to disambiguate calls. */
+template<auto Func>
+inline constexpr connect_arg_t<Func> 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<typename>
+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<typename Ret, typename... Args>
+class delegate<Ret(Args...)> {
+    template<auto Candidate, std::size_t... Index>
+    [[nodiscard]] auto wrap(std::index_sequence<Index...>) ENTT_NOEXCEPT {
+        return [](const void *, Args... args) -> Ret {
+            [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
+            return static_cast<Ret>(std::invoke(Candidate, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...));
+        };
+    }
+
+    template<auto Candidate, typename Type, std::size_t... Index>
+    [[nodiscard]] auto wrap(Type &, std::index_sequence<Index...>) ENTT_NOEXCEPT {
+        return [](const void *payload, Args... args) -> Ret {
+            [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
+            Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
+            return static_cast<Ret>(std::invoke(Candidate, *curr, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(arguments))...));
+        };
+    }
+
+    template<auto Candidate, typename Type, std::size_t... Index>
+    [[nodiscard]] auto wrap(Type *, std::index_sequence<Index...>) ENTT_NOEXCEPT {
+        return [](const void *payload, Args... args) -> Ret {
+            [[maybe_unused]] const auto arguments = std::forward_as_tuple(std::forward<Args>(args)...);
+            Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
+            return static_cast<Ret>(std::invoke(Candidate, curr, std::forward<type_list_element_t<Index, type_list<Args...>>>(std::get<Index>(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
+        : fn{nullptr},
+          data{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<auto Candidate>
+    delegate(connect_arg_t<Candidate>) ENTT_NOEXCEPT {
+        connect<Candidate>();
+    }
+
+    /**
+     * @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<auto Candidate, typename Type>
+    delegate(connect_arg_t<Candidate>, Type &&value_or_instance) ENTT_NOEXCEPT {
+        connect<Candidate>(std::forward<Type>(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<auto Candidate>
+    void connect() ENTT_NOEXCEPT {
+        data = nullptr;
+
+        if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Args...>) {
+            fn = [](const void *, Args... args) -> Ret {
+                return Ret(std::invoke(Candidate, std::forward<Args>(args)...));
+            };
+        } else if constexpr(std::is_member_pointer_v<decltype(Candidate)>) {
+            fn = wrap<Candidate>(internal::index_sequence_for<type_list_element_t<0, type_list<Args...>>>(internal::function_pointer_t<decltype(Candidate)>{}));
+        } else {
+            fn = wrap<Candidate>(internal::index_sequence_for(internal::function_pointer_t<decltype(Candidate)>{}));
+        }
+    }
+
+    /**
+     * @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.<br/>
+     * 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<auto Candidate, typename Type>
+    void connect(Type &value_or_instance) ENTT_NOEXCEPT {
+        data = &value_or_instance;
+
+        if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Type &, Args...>) {
+            fn = [](const void *payload, Args... args) -> Ret {
+                Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
+                return Ret(std::invoke(Candidate, *curr, std::forward<Args>(args)...));
+            };
+        } else {
+            fn = wrap<Candidate>(value_or_instance, internal::index_sequence_for(internal::function_pointer_t<decltype(Candidate), Type>{}));
+        }
+    }
+
+    /**
+     * @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<auto Candidate, typename Type>
+    void connect(Type *value_or_instance) ENTT_NOEXCEPT {
+        data = value_or_instance;
+
+        if constexpr(std::is_invocable_r_v<Ret, decltype(Candidate), Type *, Args...>) {
+            fn = [](const void *payload, Args... args) -> Ret {
+                Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
+                return Ret(std::invoke(Candidate, curr, std::forward<Args>(args)...));
+            };
+        } else {
+            fn = wrap<Candidate>(value_or_instance, internal::index_sequence_for(internal::function_pointer_t<decltype(Candidate), Type>{}));
+        }
+    }
+
+    /**
+     * @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.<br/>
+     * 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 {
+        fn = function;
+        data = payload;
+    }
+
+    /**
+     * @brief Resets a delegate.
+     *
+     * After a reset, a delegate cannot be invoked anymore.
+     */
+    void reset() ENTT_NOEXCEPT {
+        fn = nullptr;
+        data = 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 *instance() const ENTT_NOEXCEPT {
+        return data;
+    }
+
+    /**
+     * @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<bool>(*this), "Uninitialized delegate");
+        return fn(data, std::forward<Args>(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 test also data
+        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<Ret(Args...)> &other) const ENTT_NOEXCEPT {
+        return fn == other.fn && data == other.data;
+    }
+
+private:
+    function_type *fn;
+    const void *data;
+};
+
+/**
+ * @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<typename Ret, typename... Args>
+[[nodiscard]] bool operator!=(const delegate<Ret(Args...)> &lhs, const delegate<Ret(Args...)> &rhs) ENTT_NOEXCEPT {
+    return !(lhs == rhs);
+}
+
+/**
+ * @brief Deduction guide.
+ * @tparam Candidate Function or member to connect to the delegate.
+ */
+template<auto Candidate>
+delegate(connect_arg_t<Candidate>) -> delegate<std::remove_pointer_t<internal::function_pointer_t<decltype(Candidate)>>>;
+
+/**
+ * @brief Deduction guide.
+ * @tparam Candidate Function or member to connect to the delegate.
+ * @tparam Type Type of class or type of payload.
+ */
+template<auto Candidate, typename Type>
+delegate(connect_arg_t<Candidate>, Type &&) -> delegate<std::remove_pointer_t<internal::function_pointer_t<decltype(Candidate), Type>>>;
+
+/**
+ * @brief Deduction guide.
+ * @tparam Ret Return type of a function type.
+ * @tparam Args Types of arguments of a function type.
+ */
+template<typename Ret, typename... Args>
+delegate(Ret (*)(const void *, Args...), const void * = nullptr) -> delegate<Ret(Args...)>;
+
+} // namespace entt
+
+#endif

+ 264 - 0
Dependencies/include/entt/signal/dispatcher.hpp

@@ -0,0 +1,264 @@
+#ifndef ENTT_SIGNAL_DISPATCHER_HPP
+#define ENTT_SIGNAL_DISPATCHER_HPP
+
+#include <cstddef>
+#include <memory>
+#include <type_traits>
+#include <utility>
+#include <vector>
+#include "../config/config.h"
+#include "../container/dense_hash_map.hpp"
+#include "../core/fwd.hpp"
+#include "../core/type_info.hpp"
+#include "../core/utility.hpp"
+#include "sigh.hpp"
+
+namespace entt {
+
+/**
+ * @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.<br/>
+ * 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.
+ */
+class dispatcher {
+    struct basic_pool {
+        virtual ~basic_pool() = default;
+        virtual void publish() = 0;
+        virtual void disconnect(void *) = 0;
+        virtual void clear() ENTT_NOEXCEPT = 0;
+    };
+
+    template<typename Event>
+    struct pool_handler final: basic_pool {
+        static_assert(std::is_same_v<Event, std::decay_t<Event>>, "Invalid event type");
+
+        using signal_type = sigh<void(Event &)>;
+        using sink_type = typename signal_type::sink_type;
+
+        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]] sink_type bucket() ENTT_NOEXCEPT {
+            return sink_type{signal};
+        }
+
+        template<typename... Args>
+        void trigger(Args &&...args) {
+            Event instance{std::forward<Args>(args)...};
+            signal.publish(instance);
+        }
+
+        template<typename... Args>
+        void enqueue(Args &&...args) {
+            if constexpr(std::is_aggregate_v<Event>) {
+                events.push_back(Event{std::forward<Args>(args)...});
+            } else {
+                events.emplace_back(std::forward<Args>(args)...);
+            }
+        }
+
+    private:
+        signal_type signal{};
+        std::vector<Event> events;
+    };
+
+    template<typename Event>
+    [[nodiscard]] pool_handler<Event> &assure() {
+        if(auto &&ptr = pools[type_hash<Event>::value()]; !ptr) {
+            auto *cpool = new pool_handler<Event>{};
+            ptr.reset(cpool);
+            return *cpool;
+        } else {
+            return static_cast<pool_handler<Event> &>(*ptr);
+        }
+    }
+
+public:
+    /*! @brief Default constructor. */
+    dispatcher() = default;
+
+    /*! @brief Default move constructor. */
+    dispatcher(dispatcher &&) = default;
+
+    /*! @brief Default move assignment operator. @return This dispatcher. */
+    dispatcher &operator=(dispatcher &&) = default;
+
+    /**
+     * @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 _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.
+     * @return A temporary sink object.
+     */
+    template<typename Event>
+    [[nodiscard]] auto sink() {
+        return assure<Event>().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.
+     *
+     * @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.
+     */
+    template<typename Event, typename... Args>
+    void trigger(Args &&...args) {
+        assure<Event>().trigger(std::forward<Args>(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.
+     * @param event An instance of the given type of event.
+     */
+    template<typename Event>
+    void trigger(Event &&event) {
+        assure<std::decay_t<Event>>().trigger(std::forward<Event>(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 args Arguments to use to construct the event.
+     */
+    template<typename Event, typename... Args>
+    void enqueue(Args &&...args) {
+        assure<Event>().enqueue(std::forward<Args>(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 event An instance of the given type of event.
+     */
+    template<typename Event>
+    void enqueue(Event &&event) {
+        assure<std::decay_t<Event>>().enqueue(std::forward<Event>(event));
+    }
+
+    /**
+     * @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<typename Type>
+    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<typename Type>
+    void disconnect(Type *value_or_instance) {
+        for(auto &&cpool: pools) {
+            cpool.second->disconnect(value_or_instance);
+        }
+    }
+
+    /**
+     * @brief Discards all the events queued so far.
+     *
+     * If no types are provided, the dispatcher will clear all the existing
+     * pools.
+     *
+     * @tparam Event Type of events to discard.
+     */
+    template<typename... Event>
+    void clear() {
+        if constexpr(sizeof...(Event) == 0) {
+            for(auto &&cpool: pools) {
+                cpool.second->clear();
+            }
+        } else {
+            (assure<Event>().clear(), ...);
+        }
+    }
+
+    /**
+     * @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.
+     */
+    template<typename Event>
+    void update() {
+        assure<Event>().publish();
+    }
+
+    /**
+     * @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.
+     */
+    void update() const {
+        for(auto &&cpool: pools) {
+            cpool.second->publish();
+        }
+    }
+
+private:
+    dense_hash_map<id_type, std::unique_ptr<basic_pool>, identity> pools;
+};
+
+} // namespace entt
+
+#endif

+ 316 - 0
Dependencies/include/entt/signal/emitter.hpp

@@ -0,0 +1,316 @@
+#ifndef ENTT_SIGNAL_EMITTER_HPP
+#define ENTT_SIGNAL_EMITTER_HPP
+
+#include <algorithm>
+#include <functional>
+#include <iterator>
+#include <list>
+#include <memory>
+#include <type_traits>
+#include <utility>
+#include "../config/config.h"
+#include "../container/dense_hash_map.hpp"
+#include "../core/fwd.hpp"
+#include "../core/type_info.hpp"
+#include "../core/utility.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<my_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.<br/>
+ * 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<typename Derived>
+class emitter {
+    struct basic_pool {
+        virtual ~basic_pool() = default;
+        virtual bool empty() const ENTT_NOEXCEPT = 0;
+        virtual void clear() ENTT_NOEXCEPT = 0;
+    };
+
+    template<typename Event>
+    struct pool_handler final: basic_pool {
+        static_assert(std::is_same_v<Event, std::decay_t<Event>>, "Invalid event type");
+
+        using listener_type = std::function<void(Event &, Derived &)>;
+        using element_type = std::pair<bool, listener_type>;
+        using container_type = std::list<element_type>;
+        using connection_type = typename container_type::iterator;
+
+        [[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);
+        }
+
+        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));
+        }
+
+        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);
+            }
+        }
+
+        void publish(Event &event, Derived &ref) {
+            container_type swap_list;
+            once_list.swap(swap_list);
+
+            publishing = true;
+
+            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;
+
+            on_list.remove_if([](auto &&element) { return element.first; });
+        }
+
+    private:
+        bool publishing{false};
+        container_type once_list{};
+        container_type on_list{};
+    };
+
+    template<typename Event>
+    [[nodiscard]] pool_handler<Event> *assure() {
+        if(auto &&ptr = pools[type_hash<Event>::value()]; !ptr) {
+            auto *cpool = new pool_handler<Event>{};
+            ptr.reset(cpool);
+            return cpool;
+        } else {
+            return static_cast<pool_handler<Event> *>(ptr.get());
+        }
+    }
+
+    template<typename Event>
+    [[nodiscard]] const pool_handler<Event> *assure() const {
+        const auto it = pools.find(type_hash<Event>::value());
+        return (it == pools.cend()) ? nullptr : static_cast<const pool_handler<Event> *>(it->second.get());
+    }
+
+public:
+    /** @brief Type of listeners accepted for the given event. */
+    template<typename Event>
+    using listener = typename pool_handler<Event>::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.<br/>
+     * It can be used to break connections still in use.
+     *
+     * @tparam Event Type of event for which the connection is created.
+     */
+    template<typename Event>
+    struct connection: private pool_handler<Event>::connection_type {
+        /** @brief Event emitters are friend classes of connections. */
+        friend class emitter;
+
+        /*! @brief Default constructor. */
+        connection() = default;
+
+        /**
+         * @brief Creates a connection that wraps its underlying instance.
+         * @param conn A connection object to wrap.
+         */
+        connection(typename pool_handler<Event>::connection_type conn)
+            : pool_handler<Event>::connection_type{std::move(conn)} {}
+    };
+
+    /*! @brief Default constructor. */
+    emitter() = default;
+
+    /*! @brief Default destructor. */
+    virtual ~emitter() {
+        static_assert(std::is_base_of_v<emitter<Derived>, 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;
+
+    /**
+     * @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.
+     */
+    template<typename Event, typename... Args>
+    void publish(Args &&...args) {
+        Event instance{std::forward<Args>(args)...};
+        assure<Event>()->publish(instance, *static_cast<Derived *>(this));
+    }
+
+    /**
+     * @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.<br/>
+     * 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<typename Event>
+    connection<Event> on(listener<Event> instance) {
+        return assure<Event>()->on(std::move(instance));
+    }
+
+    /**
+     * @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.<br/>
+     * 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<typename Event>
+    connection<Event> once(listener<Event> instance) {
+        return assure<Event>()->once(std::move(instance));
+    }
+
+    /**
+     * @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.
+     */
+    template<typename Event>
+    void erase(connection<Event> conn) {
+        assure<Event>()->erase(std::move(conn));
+    }
+
+    /**
+     * @brief Disconnects all the listeners for the given event type.
+     *
+     * All the connections previously returned for the given event are
+     * invalidated. Using them results in undefined behavior.
+     *
+     * @tparam Event Type of event to reset.
+     */
+    template<typename Event>
+    void clear() {
+        assure<Event>()->clear();
+    }
+
+    /**
+     * @brief Disconnects all the listeners.
+     *
+     * All the connections previously returned are invalidated. Using them
+     * results in undefined behavior.
+     */
+    void clear() ENTT_NOEXCEPT {
+        for(auto &&cpool: pools) {
+            cpool.second->clear();
+        }
+    }
+
+    /**
+     * @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.
+     */
+    template<typename Event>
+    [[nodiscard]] bool empty() const {
+        const auto *cpool = assure<Event>();
+        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.
+     */
+    [[nodiscard]] bool empty() const ENTT_NOEXCEPT {
+        return std::all_of(pools.cbegin(), pools.cend(), [](auto &&cpool) {
+            return cpool.second->empty();
+        });
+    }
+
+private:
+    dense_hash_map<id_type, std::unique_ptr<basic_pool>, identity> pools{};
+};
+
+} // namespace entt
+
+#endif

+ 28 - 0
Dependencies/include/entt/signal/fwd.hpp

@@ -0,0 +1,28 @@
+#ifndef ENTT_SIGNAL_FWD_HPP
+#define ENTT_SIGNAL_FWD_HPP
+
+#include <memory>
+
+namespace entt {
+
+template<typename>
+class delegate;
+
+class dispatcher;
+
+template<typename>
+class emitter;
+
+class connection;
+
+struct scoped_connection;
+
+template<typename>
+class sink;
+
+template<typename Type, typename = std::allocator<delegate<Type>>>
+class sigh;
+
+} // namespace entt
+
+#endif

+ 611 - 0
Dependencies/include/entt/signal/sigh.hpp

@@ -0,0 +1,611 @@
+#ifndef ENTT_SIGNAL_SIGH_HPP
+#define ENTT_SIGNAL_SIGH_HPP
+
+#include <algorithm>
+#include <functional>
+#include <iterator>
+#include <type_traits>
+#include <utility>
+#include <vector>
+#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<typename Type>
+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<typename Type, typename Allocator>
+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<typename Ret, typename... Args, typename Allocator>
+class sigh<Ret(Args...), Allocator> {
+    /*! @brief A sink is allowed to modify a signal. */
+    friend class sink<sigh<Ret(Args...), Allocator>>;
+
+    using allocator_traits = std::allocator_traits<Allocator>;
+    using alloc = typename allocator_traits::template rebind_alloc<delegate<Ret(Args...)>>;
+    using alloc_traits = typename std::allocator_traits<alloc>;
+
+    using container_type = std::vector<delegate<Ret(Args...)>, 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<sigh<Ret(Args...), Allocator>>;
+
+    /*! @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 Default 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 Default 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 Default 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 Default 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<typename Class>
+    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<typename Func>
+    void collect(Func func, Args... args) const {
+        for(auto &&call: calls) {
+            if constexpr(std::is_void_v<Ret>) {
+                if constexpr(std::is_invocable_r_v<bool, Func>) {
+                    call(args...);
+                    if(func()) { break; }
+                } else {
+                    call(args...);
+                    func();
+                }
+            } else {
+                if constexpr(std::is_invocable_r_v<bool, Func, Ret>) {
+                    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<typename>
+    friend class sink;
+
+    connection(delegate<void(void *)> 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<bool>(disconnect);
+    }
+
+    /*! @brief Breaks the connection. */
+    void release() {
+        if(disconnect) {
+            disconnect(signal);
+            disconnect.reset();
+        }
+    }
+
+private:
+    delegate<void(void *)> 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.<br/>
+ * 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<bool>(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.<br/>
+ * 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<typename Ret, typename... Args, typename Allocator>
+class sink<sigh<Ret(Args...), Allocator>> {
+    using signal_type = sigh<Ret(Args...), Allocator>;
+    using difference_type = typename std::iterator_traits<typename decltype(signal_type::calls)::iterator>::difference_type;
+
+    template<auto Candidate, typename Type>
+    static void release(Type value_or_instance, void *signal) {
+        sink{*static_cast<signal_type *>(signal)}.disconnect<Candidate>(value_or_instance);
+    }
+
+    template<auto Candidate>
+    static void release(void *signal) {
+        sink{*static_cast<signal_type *>(signal)}.disconnect<Candidate>();
+    }
+
+public:
+    /**
+     * @brief Constructs a sink that is allowed to modify a given signal.
+     * @param ref A valid reference to a signal object.
+     */
+    sink(sigh<Ret(Args...), Allocator> &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<auto Function>
+    [[nodiscard]] sink before() {
+        delegate<Ret(Args...)> call{};
+        call.template connect<Function>();
+
+        const auto &calls = signal->calls;
+        const auto it = std::find(calls.cbegin(), calls.cend(), std::move(call));
+
+        sink other{*this};
+        other.offset = std::distance(it, calls.cend());
+        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<auto Candidate, typename Type>
+    [[nodiscard]] sink before(Type &&value_or_instance) {
+        delegate<Ret(Args...)> call{};
+        call.template connect<Candidate>(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 = std::distance(it, calls.cend());
+        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<typename Type>
+    [[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<typename Type>
+    [[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.instance() == value_or_instance;
+            });
+
+            other.offset = std::distance(it, calls.cend());
+        }
+
+        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<auto Candidate>
+    connection connect() {
+        disconnect<Candidate>();
+
+        delegate<Ret(Args...)> call{};
+        call.template connect<Candidate>();
+        signal->calls.insert(signal->calls.end() - offset, std::move(call));
+
+        delegate<void(void *)> conn{};
+        conn.template connect<&release<Candidate>>();
+        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.<br/>
+     * 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<auto Candidate, typename Type>
+    connection connect(Type &&value_or_instance) {
+        disconnect<Candidate>(value_or_instance);
+
+        delegate<Ret(Args...)> call{};
+        call.template connect<Candidate>(value_or_instance);
+        signal->calls.insert(signal->calls.end() - offset, std::move(call));
+
+        delegate<void(void *)> conn{};
+        conn.template connect<&release<Candidate, Type>>(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<auto Candidate>
+    void disconnect() {
+        auto &calls = signal->calls;
+        delegate<Ret(Args...)> call{};
+        call.template connect<Candidate>();
+        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<auto Candidate, typename Type>
+    void disconnect(Type &&value_or_instance) {
+        auto &calls = signal->calls;
+        delegate<Ret(Args...)> call{};
+        call.template connect<Candidate>(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<typename Type>
+    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<typename Type>
+    void disconnect(Type *value_or_instance) {
+        if(value_or_instance) {
+            auto &calls = signal->calls;
+            auto predicate = [value_or_instance](const auto &delegate) { return delegate.instance() == 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<typename Ret, typename... Args, typename Allocator>
+sink(sigh<Ret(Args...), Allocator> &) -> sink<sigh<Ret(Args...), Allocator>>;
+
+} // namespace entt
+
+#endif

+ 6 - 0
Dependencies/include/imgui/backends/imgui_impl_opengl3_loader.h

@@ -90,6 +90,9 @@ extern "C" {
 ** used to make the header, and the header can be found at
 **   https://github.com/KhronosGroup/OpenGL-Registry
 */
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
 #if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__)
 #ifndef WIN32_LEAN_AND_MEAN
 #define WIN32_LEAN_AND_MEAN 1
@@ -573,6 +576,9 @@ extern "C" {
 #ifndef WIN32_LEAN_AND_MEAN
 #define WIN32_LEAN_AND_MEAN 1
 #endif
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
 #include <windows.h>
 
 static HMODULE libgl;

+ 3 - 0
Dependencies/include/imgui/imgui.cpp

@@ -820,6 +820,9 @@ CODE
 #define NOMINMAX
 #endif
 #ifndef __MINGW32__
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
 #include <Windows.h>        // _wfopen, OpenClipboard
 #else
 #include <windows.h>

+ 186 - 117
Praxis3D/Data/Maps/componentTest.pmap

@@ -4,9 +4,6 @@
 		{
 			"Name": "root",
 			"ID": "0",
-			"LocalPosition": "0.0f, 0.0f, 0.0f",
-			"LocalRotation": "0.0f, 0.0f, 0.0f",
-			"LocalScale": "1.0f, 1.0f, 1.0f",
 			"Children":
 			[
 				{
@@ -28,9 +25,18 @@
 					"ID": "6"
 				}
 			],
+			"World":
+			{
+				"SpatialComponent":
+				{
+					"LocalPosition": "0.0f, 0.0f, 0.0f",
+					"LocalRotation": "0.0f, 0.0f, 0.0f",
+					"LocalScale": "1.0f, 1.0f, 1.0f"
+				}
+			},
 			"Script":
 			{
-				"Lua":
+				"LuaComponent":
 				{
 					"Filename": "Window_controls.lua"
 				}
@@ -49,7 +55,7 @@
 			},
 			"Script":
 			{
-				"Lua":
+				"LuaComponent":
 				{
 					"Filename": "GUI_test.lua"
 				}
@@ -57,11 +63,17 @@
 		},
 		{
 			"ID": "2",
-			"LocalPosition": "0.0f, 0.5f, 0.0f",
-			"LocalRotation": "0.0f, 45.0f, 0.0f",
-			"LocalScale": "1.0f, 1.0f, 1.0f",
 			"Name": "Terrain 1",
 			"Parent": "0",
+			"World":
+			{
+				"SpatialComponent":
+				{
+					"LocalPosition": "0.0f, 0.5f, 0.0f",
+					"LocalRotation": "0.0f, 45.0f, 0.0f",
+					"LocalScale": "1.0f, 1.0f, 1.0f"
+				}
+			},
 			"Physics":
 			{
 				"RigidBody":
@@ -76,43 +88,46 @@
 			},
 			"Rendering":
 			{
-				"Models":
-				[
-					{
-						"Filename": "plane.obj",
-						"Meshes":
-						[
-							{
-								"Index": "0",
-								"AlphaThreshold": "0.0f",
-								"HeightScale": "0.0f",
-								"Materials":
+				"ModelComponent":
+				{
+					"Models":
+					[
+						{
+							"Filename": "plane.obj",
+							"Meshes":
+							[
 								{
-									"Diffuse":
-									{
-										"Filename": "Metal_CleanPaintedWithChips_2k_alb.tga",
-										"TextureScale": "1.0f, 1.0f"
-									},
-									"Normal":
+									"Index": "0",
+									"AlphaThreshold": "0.0f",
+									"HeightScale": "0.0f",
+									"Materials":
 									{
-										"Filename": "Metal_CleanPaintedWithChips_2k_n.tga",
-										"TextureScale": "1.0f, 1.0f"
-									},
-									"Emissive":
-									{
-										"Filename": "default_emissive.png",
-										"TextureScale": "1.0f, 1.0f"
-									},
-									"RMHAO":
-									{
-										"Filename": "Metal_CleanPaintedWithChips_RMHAO.tga",
-										"TextureScale": "1.0f, 1.0f"
+										"Diffuse":
+										{
+											"Filename": "Metal_CleanPaintedWithChips_2k_alb.tga",
+											"TextureScale": "1.0f, 1.0f"
+										},
+										"Normal":
+										{
+											"Filename": "Metal_CleanPaintedWithChips_2k_n.tga",
+											"TextureScale": "1.0f, 1.0f"
+										},
+										"Emissive":
+										{
+											"Filename": "default_emissive.png",
+											"TextureScale": "1.0f, 1.0f"
+										},
+										"RMHAO":
+										{
+											"Filename": "Metal_CleanPaintedWithChips_RMHAO.tga",
+											"TextureScale": "1.0f, 1.0f"
+										}
 									}
 								}
-							}
-						]
-					}
-				]
+							]
+						}
+					]
+				}
 //				"Shaders":
 //				{
 //					"FragmentShader": "geometryPassInf.frag",
@@ -122,106 +137,130 @@
 		},
 		{
 			"ID": "3",
-			"LocalPosition": "0.0f, 5.0f, 0.0f",
-			"LocalRotation": "0.0f, 45.0f, 0.0f",
-			"LocalScale": "2.0f, 2.0f, 2.0f",
 			"Name": "Cube 1",
 			"Parent": "0",
+			"World":
+			{
+				"SpatialComponent":
+				{
+					"LocalPosition": "0.0f, 5.0f, 0.0f",
+					"LocalRotation": "0.0f, 45.0f, 0.0f",
+					"LocalScale": "2.0f, 2.0f, 2.0f"
+				}
+			},
 			"Rendering":
 			{
-				"Models":
-				[
-					{
-						"Filename": "cube.obj",
-						"Meshes":
-						[
-							{
-								"Index": "0",
-								"AlphaThreshold": "0.0f",
-								"HeightScale": "0.0f",
-								"Materials":
+				"ModelComponent":
+				{
+					"Models":
+					[
+						{
+							"Filename": "cube.obj",
+							"Meshes":
+							[
 								{
-									"Diffuse":
+									"Index": "0",
+									"AlphaThreshold": "0.0f",
+									"HeightScale": "0.0f",
+									"Materials":
 									{
-										"Filename": "Metal_CleanPaintedWithChips_2k_alb.tga",
-										"TextureScale": "2.0f, 2.0f"
-									},
-									"Normal":
-									{
-										"Filename": "Metal_CleanPaintedWithChips_2k_n.tga",
-										"TextureScale": "1.0f, 1.0f"
-									},
-									"Emissive":
-									{
-										"Filename": "default_emissive.png",
-										"TextureScale": "1.0f, 1.0f"
-									},
-									"RMHAO":
-									{
-										"Filename": "Metal_CleanPaintedWithChips_RMHAO3.tga",
-										"TextureScale": "1.0f, 1.0f"
+										"Diffuse":
+										{
+											"Filename": "Metal_CleanPaintedWithChips_2k_alb.tga",
+											"TextureScale": "2.0f, 2.0f"
+										},
+										"Normal":
+										{
+											"Filename": "Metal_CleanPaintedWithChips_2k_n.tga",
+											"TextureScale": "1.0f, 1.0f"
+										},
+										"Emissive":
+										{
+											"Filename": "default_emissive.png",
+											"TextureScale": "1.0f, 1.0f"
+										},
+										"RMHAO":
+										{
+											"Filename": "Metal_CleanPaintedWithChips_RMHAO3.tga",
+											"TextureScale": "1.0f, 1.0f"
+										}
 									}
 								}
-							}
-						]
-					}
-				]
+							]
+						}
+					]
+				}
 			}
 		},
 		{
 			"ID": "4",
-			"LocalPosition": "15.0f, 5.0f, 0.0f",
-			"LocalRotation": "0.0f, 45.0f, 0.0f",
-			"LocalScale": "4.0f, 4.0f, 4.0f",
 			"Name": "Sphere 1",
 			"Parent": "0",
+			"World":
+			{
+				"SpatialComponent":
+				{
+					"LocalPosition": "15.0f, 5.0f, 0.0f",
+					"LocalRotation": "0.0f, 45.0f, 0.0f",
+					"LocalScale": "4.0f, 4.0f, 4.0f"
+				}
+			},
 			"Rendering":
 			{
-				"Models":
-				[
-					{
-						"Filename": "sphereNew4.3DS",
-						"Meshes":
-						[
-							{
-								"Index": "0",
-								"AlphaThreshold": "0.0f",
-								"HeightScale": "0.0f",
-								"Materials":
+				"ModelComponent":
+				{
+					"Models":
+					[
+						{
+							"Filename": "sphereNew4.3DS",
+							"Meshes":
+							[
 								{
-									"Diffuse":
-									{
-										"Filename": "test_albedo.png",
-										"TextureScale": "1.0f, 1.0f"
-									},
-									"RMHAO":
+									"Index": "0",
+									"AlphaThreshold": "0.0f",
+									"HeightScale": "0.0f",
+									"Materials":
 									{
-										"Filename": "test2_RM.png",
-										"TextureScale": "1.0f, 1.0f"
+										"Diffuse":
+										{
+											"Filename": "test_albedo.png",
+											"TextureScale": "1.0f, 1.0f"
+										},
+										"RMHAO":
+										{
+											"Filename": "test2_RM.png",
+											"TextureScale": "1.0f, 1.0f"
+										}
 									}
 								}
-							}
-						]
-					}
-				]
+							]
+						}
+					]
+				}
 			}
 		},
 		{
 			"ID": "10",
 			"Name": "Camera 1",
 			"Parent": "0",
-			"LocalPosition": "0.0f, 1.5f, 3.0f",
-			"LocalRotation": "30.0f, 0.0f, 0.0f",
+			"World":
+			{
+				"SpatialComponent":
+				{
+					"LocalPosition": "0.0f, 10.5f, 3.0f",
+					"LocalRotation": "30.0f, 0.0f, 0.0f"
+				}
+			},
 			"Rendering":
 			{
-				"Camera":
+				"CameraComponent":
 				{
 
 				}
 			},
 			"Script":
 			{
-				"Lua":
+				"LuaComponent":
 				{
 					"Filename": "Camera_free.lua",
 					"Variables":
@@ -242,10 +281,16 @@
 			"ID": "11",
 			"Name": "Directional Light 1",
 			"Parent": "0",
-			"LocalRotation": "-45.0f, 45.0f, 0.0f",
+			"World":
+			{
+				"SpatialComponent":
+				{
+					"LocalRotation": "-45.0f, 45.0f, 0.0f"
+				}
+			},
 			"Rendering":
 			{
-				"Lighting":
+				"LightComponent":
 				{
 					"Type": "DirectionalLight",
 					"Color": "1.0f, 0.0f, 0.0f",
@@ -257,10 +302,16 @@
 			"ID": "12",
 			"Name": "PointLight 1",
 			"Parent": "0",
-			"LocalPosition": "20.0f, 2.0f, 20.0f",
+			"World":
+			{
+				"SpatialComponent":
+				{
+					"LocalPosition": "20.0f, 2.0f, 20.0f"
+				}
+			},
 			"Rendering":
 			{
-				"Lighting":
+				"LightComponent":
 				{
 					"Type": "PointLight",
 					"Color": "1.0f, 1.0f, 1.0f",
@@ -272,10 +323,16 @@
 			"ID": "13",
 			"Name": "PointLight 2",
 			"Parent": "0",
-			"LocalPosition": "-20.0f, 2.0f, 20.0f",
+			"World":
+			{
+				"SpatialComponent":
+				{
+					"LocalPosition": "-20.0f, 2.0f, 20.0f"
+				}
+			},
 			"Rendering":
 			{
-				"Lighting":
+				"LightComponent":
 				{
 					"Type": "PointLight",
 					"Color": "1.0f, 1.0f, 1.0f",
@@ -287,10 +344,16 @@
 			"ID": "14",
 			"Name": "PointLight 3",
 			"Parent": "0",
-			"LocalPosition": "20.0f, 2.0f, -20.0f",
+			"World":
+			{
+				"SpatialComponent":
+				{
+					"LocalPosition": "20.0f, 2.0f, -20.0f"
+				}
+			},
 			"Rendering":
 			{
-				"Lighting":
+				"LightComponent":
 				{
 					"Type": "PointLight",
 					"Color": "1.0f, 1.0f, 1.0f",
@@ -302,10 +365,16 @@
 			"ID": "15",
 			"Name": "PointLight 4",
 			"Parent": "0",
-			"LocalPosition": "-20.0f, 2.0f, -20.0f",
+			"World":
+			{
+				"SpatialComponent":
+				{
+					"LocalPosition": "-20.0f, 2.0f, -20.0f"
+				}
+			},
 			"Rendering":
 			{
-				"Lighting":
+				"LightComponent":
 				{
 					"Type": "PointLight",
 					"Color": "1.0f, 1.0f, 1.0f",

+ 339 - 0
Praxis3D/Data/Maps/componentTest2.pmap

@@ -0,0 +1,339 @@
+{
+	"GameObject":
+	[
+		{
+			"Name": "root",
+			"ID": "0",
+			"Children":
+			[
+				{
+					"ID": "1"
+				},
+				{
+					"ID": "2"
+				},
+				{
+					"ID": "3"
+				},
+				{
+					"ID": "4"
+				},
+				{
+					"ID": "5"
+				},
+				{
+					"ID": "6"
+				}
+			],
+			"Components":
+			{
+				"LuaComponent":
+				{
+					"Filename": "Window_controls.lua"
+				},
+				"SpatialComponent":
+				{
+					"LocalPosition": "0.0f, 0.0f, 0.0f",
+					"LocalRotation": "0.0f, 0.0f, 0.0f",
+					"LocalScale": "1.0f, 1.0f, 1.0f"
+				}
+			}
+		},
+		{
+			"Name": "GUI",
+			"ID": "1",
+			"Parent": "0",
+			"Components":
+			{
+				"LuaComponent":
+				{
+					"Filename": "GUI_test.lua"
+				},
+				"GUISequenceComponent":
+				{
+
+				}
+			}
+		},
+		{
+			"ID": "2",
+			"Name": "Terrain 1",
+			"Parent": "0",
+			"Components":
+			{
+				"RigidBodyComponent":
+				{
+					"Mass": "10.0f"
+				},
+				"CollisionShapeComponent":
+				{
+					"Type": "Box",
+					"Size": "50.0f, 50.0f, 50.0f"
+				},
+				"SpatialComponent":
+				{
+					"LocalPosition": "0.0f, 0.5f, 0.0f",
+					"LocalRotation": "0.0f, 45.0f, 0.0f",
+					"LocalScale": "1.0f, 1.0f, 1.0f"
+				},
+				"ModelComponent":
+				{
+					"Models":
+					[
+						{
+							"Filename": "plane.obj",
+							"Meshes":
+							[
+								{
+									"Index": "0",
+									"AlphaThreshold": "0.0f",
+									"HeightScale": "0.0f",
+									"Materials":
+									{
+										"Diffuse":
+										{
+											"Filename": "Metal_CleanPaintedWithChips_2k_alb.tga",
+											"TextureScale": "1.0f, 1.0f"
+										},
+										"Normal":
+										{
+											"Filename": "Metal_CleanPaintedWithChips_2k_n.tga",
+											"TextureScale": "1.0f, 1.0f"
+										},
+										"Emissive":
+										{
+											"Filename": "default_emissive.png",
+											"TextureScale": "1.0f, 1.0f"
+										},
+										"RMHAO":
+										{
+											"Filename": "Metal_CleanPaintedWithChips_RMHAO.tga",
+											"TextureScale": "1.0f, 1.0f"
+										}
+									}
+								}
+							]
+						}
+					]
+					//				"Shaders":
+					//				{
+					//					"FragmentShader": "geometryPassInf.frag",
+					//					"VertexShader": "geometryPassInf.vert"
+					//				}
+				}
+			}
+		},
+		{
+			"ID": "3",
+			"Name": "Cube 1",
+			"Parent": "0",
+			"Components":
+			{
+				"SpatialComponent":
+				{
+					"LocalPosition": "0.0f, 5.0f, 0.0f",
+					"LocalRotation": "0.0f, 45.0f, 0.0f",
+					"LocalScale": "2.0f, 2.0f, 2.0f"
+				},
+				"Rendering":
+				{
+					"ModelComponent":
+					[
+						{
+							"Filename": "cube.obj",
+							"Meshes":
+							[
+								{
+									"Index": "0",
+									"AlphaThreshold": "0.0f",
+									"HeightScale": "0.0f",
+									"Materials":
+									{
+										"Diffuse":
+										{
+											"Filename": "Metal_CleanPaintedWithChips_2k_alb.tga",
+											"TextureScale": "2.0f, 2.0f"
+										},
+										"Normal":
+										{
+											"Filename": "Metal_CleanPaintedWithChips_2k_n.tga",
+											"TextureScale": "1.0f, 1.0f"
+										},
+										"Emissive":
+										{
+											"Filename": "default_emissive.png",
+											"TextureScale": "1.0f, 1.0f"
+										},
+										"RMHAO":
+										{
+											"Filename": "Metal_CleanPaintedWithChips_RMHAO3.tga",
+											"TextureScale": "1.0f, 1.0f"
+										}
+									}
+								}
+							]
+						}
+					]
+				}
+			}
+		},
+		{
+			"ID": "10",
+			"Name": "Camera 1",
+			"Parent": "0",
+			"Components":
+			{
+				"SpatialComponent":
+				{
+					"LocalPosition": "0.0f, 1.5f, 3.0f",
+					"LocalRotation": "30.0f, 0.0f, 0.0f"
+				},
+				"CameraComponent":
+				{
+
+				},
+				"LuaComponent":
+				{
+					"Filename": "Camera_free.lua",
+					"Variables":
+					[
+						{
+							"Name": "cameraSpeed",
+							"Value": "10.0f"
+						},
+						{
+							"Name": "cameraSpeedMultiplier",
+							"Value": "10.0f"
+						}
+					]
+				}
+			}
+		},
+		{
+			"ID": "11",
+			"Name": "Directional Light 1",
+			"Parent": "0",
+			"Components":
+			{
+				"SpatialComponent":
+				{
+					"LocalRotation": "-45.0f, 45.0f, 0.0f"
+				},
+				"LightComponent":
+				{
+					"Type": "DirectionalLight",
+					"Color": "1.0f, 0.0f, 0.0f",
+					"Intensity": "0.2f"
+				}
+			}
+		},
+		{
+			"ID": "12",
+			"Name": "PointLight 1",
+			"Parent": "0",
+			"Components":
+			{
+				"SpatialComponent":
+				{
+					"LocalPosition": "20.0f, 2.0f, 20.0f"
+				},
+				"LightComponent":
+				{
+					"Type": "PointLight",
+					"Color": "1.0f, 1.0f, 1.0f",
+					"Intensity": "10.0f"
+				}
+			}
+		},
+		{
+			"ID": "13",
+			"Name": "PointLight 2",
+			"Parent": "0",
+			"LocalPosition": "-20.0f, 2.0f, 20.0f",
+			"Components":
+			{
+				"SpatialComponent":
+				{
+					"LocalRotation": "-45.0f, 45.0f, 0.0f"
+				},
+				"LightComponent":
+				{
+					"Type": "PointLight",
+					"Color": "1.0f, 1.0f, 1.0f",
+					"Intensity": "10.0f"
+				}
+			}
+		},
+		{
+			"ID": "14",
+			"Name": "PointLight 3",
+			"Parent": "0",
+			"LocalPosition": "20.0f, 2.0f, -20.0f",
+			"Components":
+			{
+				"SpatialComponent":
+				{
+					"LocalRotation": "-45.0f, 45.0f, 0.0f"
+				},
+				"LightComponent":
+				{
+					"Type": "PointLight",
+					"Color": "1.0f, 1.0f, 1.0f",
+					"Intensity": "10.0f"
+				}
+			}
+		},
+		{
+			"ID": "15",
+			"Name": "PointLight 4",
+			"Parent": "0",
+			"LocalPosition": "-20.0f, 2.0f, -20.0f",
+			"Components":
+			{
+				"SpatialComponent":
+				{
+					"LocalRotation": "-45.0f, 45.0f, 0.0f"
+				},
+				"LightComponent":
+				{
+					"Type": "PointLight",
+					"Color": "1.0f, 1.0f, 1.0f",
+					"Intensity": "10.0f"
+				}
+			}
+		}
+	],
+	"LoadInBackground": "0",
+	"Systems":
+	{
+		"Graphics":
+		{
+			"Scene":
+			{
+				"ObjectPoolSize": "50"
+			}
+		},
+		"Script":
+		{
+			"Scene":
+			{
+				"ObjectPoolSize": "50"
+			}
+		},
+		"Physics":
+		{
+			"Scene":
+			{
+				"ObjectPoolSize": "50",
+				"Gravity": "0.0f, -9.8f, 0.0f"
+			}
+		},
+		"World":
+		{
+			"Scene":
+			{
+				"ObjectPoolSize": "50"
+			}
+		}
+	}
+}

+ 1 - 1
Praxis3D/Data/Scripts/Window_controls.lua

@@ -26,7 +26,7 @@ function init ()
 end
 
 function update (p_deltaTime)
-	
+		
 	if closeKey:isActivated() then
 		-- Set the engine running state to false, so it is shutdown the next frame
 		setEngineRunning(false)

+ 2 - 2
Praxis3D/Data/config.ini

@@ -1,7 +1,7 @@
 window_position_x 450
 window_position_y 60
-window_size_windowed_x 640
-window_size_windowed_y 480
+window_size_windowed_x 1920
+window_size_windowed_y 1080
 window_size_fullscreen_x 1920
 window_size_fullscreen_y 1080
 fullscreen 0

+ 5 - 1
Praxis3D/Praxis3D.vcxproj

@@ -98,6 +98,7 @@
       <FunctionLevelLinking>true</FunctionLevelLinking>
       <IntrinsicFunctions>true</IntrinsicFunctions>
       <SDLCheck>true</SDLCheck>
+      <LanguageStandard>stdcpp17</LanguageStandard>
     </ClCompile>
     <Link>
       <GenerateDebugInformation>true</GenerateDebugInformation>
@@ -117,7 +118,7 @@
       <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
-      <ConformanceMode>true</ConformanceMode>
+      <ConformanceMode>false</ConformanceMode>
       <AdditionalOptions>/bigobj /MP8 %(AdditionalOptions)</AdditionalOptions>
     </ClCompile>
     <Link>
@@ -235,6 +236,7 @@
     <ClInclude Include="Source\Engine.h" />
     <ClInclude Include="Source\EngineDefinitions.h" />
     <ClInclude Include="Source\EngineState.h" />
+    <ClInclude Include="Source\EntityViewDefinitions.h" />
     <ClInclude Include="Source\EnumFactory.h" />
     <ClInclude Include="Source\EnvironmentMapObjects.h" />
     <ClInclude Include="Source\ErrorCodes.h" />
@@ -245,6 +247,7 @@
     <ClInclude Include="Source\Framebuffer.h" />
     <ClInclude Include="Source\GameLogicObject.h" />
     <ClInclude Include="Source\GameObject.h" />
+    <ClInclude Include="Source\GameObjectComponent.h" />
     <ClInclude Include="Source\GeometryBuffer.h" />
     <ClInclude Include="Source\GeometryPass.h" />
     <ClInclude Include="Source\GraphicsDataSets.h" />
@@ -315,6 +318,7 @@
     <ClInclude Include="Source\ShaderUniformUpdater.h" />
     <ClInclude Include="Source\SkyPass.h" />
     <ClInclude Include="Source\SolarTimeScript.h" />
+    <ClInclude Include="Source\SpatialComponent.h" />
     <ClInclude Include="Source\SpatialDataManager.h" />
     <ClInclude Include="Source\SpinWait.h" />
     <ClInclude Include="Source\SunScript.h" />

+ 13 - 4
Praxis3D/Praxis3D.vcxproj.filters

@@ -172,7 +172,7 @@
     <Filter Include="iImgui\Source Files">
       <UniqueIdentifier>{62cfc7e5-8f92-4ff5-bcc3-0319fec10b3c}</UniqueIdentifier>
     </Filter>
-    <Filter Include="Daa Managers">
+    <Filter Include="Data Managers">
       <UniqueIdentifier>{1d4c199c-6fd9-492b-9c8d-23af455d408f}</UniqueIdentifier>
     </Filter>
   </ItemGroup>
@@ -774,13 +774,13 @@
       <Filter>GUI\Objects\Header Files</Filter>
     </ClInclude>
     <ClInclude Include="Source\SpatialDataManager.h">
-      <Filter>Daa Managers</Filter>
+      <Filter>Data Managers</Filter>
     </ClInclude>
     <ClInclude Include="Source\GUIDataManager.h">
-      <Filter>Daa Managers</Filter>
+      <Filter>Data Managers</Filter>
     </ClInclude>
     <ClInclude Include="Source\PhysicsDataManager.h">
-      <Filter>Daa Managers</Filter>
+      <Filter>Data Managers</Filter>
     </ClInclude>
     <ClInclude Include="Source\PhysicsObject.h">
       <Filter>Physics\Objects\Header Files</Filter>
@@ -794,6 +794,15 @@
     <ClInclude Include="Source\CollisionShapeComponent.h">
       <Filter>Physics\Objects\Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="Source\SpatialComponent.h">
+      <Filter>World\Objects</Filter>
+    </ClInclude>
+    <ClInclude Include="Source\GameObjectComponent.h">
+      <Filter>World\Objects</Filter>
+    </ClInclude>
+    <ClInclude Include="Source\EntityViewDefinitions.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="Data\config.ini" />

+ 3 - 3
Praxis3D/Source/CameraComponent.h

@@ -3,11 +3,11 @@
 #include "GraphicsDataSets.h"
 #include "InheritanceObjects.h"
 
-class CameraComponent : public SystemObject, public SpatialDataManagerObject, public LoadableGraphicsObject
+class CameraComponent : public SystemObject, public LoadableGraphicsObject
 {
 	friend class RendererScene;
 public:
-	CameraComponent(SystemScene *p_systemScene, std::string p_name, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::CameraComponent)
+	CameraComponent(SystemScene *p_systemScene, std::string p_name, const EntityID p_entityID, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::CameraComponent, p_entityID)
 	{
 
 	}
@@ -41,7 +41,7 @@ public:
 			//{
 				//importError = ErrorCode::Success;
 			//}
-			if(p_properties.getPropertyID() == Properties::Camera)
+			if(p_properties.getPropertyID() == Properties::CameraComponent)
 			{
 				importError = ErrorCode::Success;
 				ErrHandlerLoc().get().log(ErrorType::Info, ErrorSource::Source_CameraComponent, m_name + " - Camera loaded");

+ 4 - 0
Praxis3D/Source/ChangeController.h

@@ -7,6 +7,10 @@
 
 #include <lmerr.h>
 #include <tchar.h>
+
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
 #include <windows.h>
 
 class TaskManager;

+ 4 - 0
Praxis3D/Source/Clock.h

@@ -1,6 +1,10 @@
 #pragma once
 
 #include <iostream>
+
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
 #include <Windows.h>
 
 #include "Config.h"

+ 3 - 0
Praxis3D/Source/CommonDefinitions.h

@@ -3,9 +3,12 @@
 #include <GL\glew.h>
 #include <functional>
 
+typedef std::uint32_t EntityID;
 typedef unsigned int UpdateCount;
 typedef std::vector<std::function<void()>> Functors;
 
+constexpr EntityID NULL_ENTITY_ID = std::numeric_limits<EntityID>::max();
+
 enum BindCommandType : unsigned int
 {
 	BindCommandType_Texture,

+ 6 - 0
Praxis3D/Source/Config.cpp

@@ -53,6 +53,7 @@ void Config::init()
 
 	// Component Variables
 	AddVariablePredef(m_componentVar, camera_component_name);
+	AddVariablePredef(m_componentVar, component_name_separator);
 	AddVariablePredef(m_componentVar, light_component_name);
 	AddVariablePredef(m_componentVar, lua_component_name);
 	AddVariablePredef(m_componentVar, model_component_name);
@@ -408,6 +409,11 @@ void Config::init()
 	AddVariablePredef(m_windowVar, mouse_release_on_lost_focus);
 	AddVariablePredef(m_windowVar, resizable);
 	AddVariablePredef(m_windowVar, vertical_sync);
+
+	for(int i = Properties::Null; i < Properties::NumberOfPropertyIDs; i++)
+	{
+		//Properties::PropertyNames[i] = GetString(static_cast<Properties::PropertyID>(i));
+	}
 }
 ErrorCode Config::loadFromFile(const std::string &p_filename)
 {

+ 13 - 0
Praxis3D/Source/Config.h

@@ -196,6 +196,7 @@ namespace Properties
 	Code(Null, = 0) \
 	/* General */ \
 	Code(ArrayEntry,) \
+	Code(Components,) \
 	Code(Default,) \
 	Code(Filename,) \
 	Code(Index,) \
@@ -242,11 +243,13 @@ namespace Properties
 	Code(Height,) \
 	Code(HeightScale,) \
 	Code(Intensity,) \
+	Code(LightComponent,) \
 	Code(Lighting,) \
 	Code(Materials,) \
 	Code(Metalness,) \
 	Code(Meshes,) \
 	Code(Models,) \
+	Code(ModelComponent,) \
 	Code(ModelObject,) \
 	Code(ModelPoolSize,) \
 	Code(NegativeX,) \
@@ -265,6 +268,7 @@ namespace Properties
 	Code(RMHAO,) \
 	Code(Roughness,) \
 	Code(Shaders,) \
+	Code(ShaderComponent,) \
 	Code(ShaderPoolSize,) \
 	Code(ShaderGraphicsObject,) \
 	Code(ShaderModelObject,) \
@@ -366,6 +370,7 @@ namespace Properties
 	Code(GameObject,) \
 	Code(ID,) \
 	Code(Parent,) \
+	Code(SpatialComponent,) \
 	Code(World,) \
 	/* End of property IDs */ \
 	Code(NumberOfPropertyIDs,) 
@@ -375,6 +380,7 @@ namespace Properties
 	{
 		GetString(Null),
 		GetString(ArrayEntry),
+		GetString(Components),
 		GetString(Default),
 		GetString(Filename),
 		GetString(Index),
@@ -419,11 +425,13 @@ namespace Properties
 		GetString(Height),
 		GetString(HeightScale),
 		GetString(Intensity),
+		GetString(LightComponent),
 		GetString(Lighting),
 		GetString(Materials),
 		GetString(Metalness),
 		GetString(Meshes),
 		GetString(Models),
+		GetString(ModelComponent),
 		GetString(ModelObject),
 		GetString(ModelPoolSize),
 		GetString(NegativeX),
@@ -442,6 +450,7 @@ namespace Properties
 		GetString(RMHAO),
 		GetString(Roughness),
 		GetString(Shaders),
+		GetString(ShaderComponent),
 		GetString(ShaderPoolSize),
 		GetString(ShaderGraphicsObject),
 		GetString(ShaderModelObject),
@@ -534,10 +543,12 @@ namespace Properties
 		GetString(MouseCapture),
 		GetString(VerticalSync),
 		GetString(WindowTitle),
+		// World
 		GetString(Children),
 		GetString(GameObject),
 		GetString(ID),
 		GetString(Parent),
+		GetString(SpatialComponent),
 		GetString(World)
 	};
 
@@ -599,6 +610,7 @@ public:
 		ComponentVariables()
 		{
 			camera_component_name = " (Camera Component)";
+			component_name_separator = " - ";
 			light_component_name = " (Light Component)";
 			lua_component_name = " (Lua Component)";
 			model_component_name = " (Model Component)";
@@ -606,6 +618,7 @@ public:
 		}
 
 		std::string camera_component_name;
+		std::string component_name_separator;
 		std::string light_component_name;
 		std::string lua_component_name;
 		std::string model_component_name;

+ 4 - 0
Praxis3D/Source/Engine.h

@@ -1,5 +1,9 @@
 #pragma once
 
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+
 #include "Clock.h"
 #include "Config.h"
 #include "ErrorCodes.h"

+ 14 - 0
Praxis3D/Source/EntityViewDefinitions.h

@@ -0,0 +1,14 @@
+#pragma once
+
+#include <entt/entt.hpp>
+
+#include "ModelComponent.h"
+#include "ShaderComponent.h"
+#include "SpatialComponent.h"
+
+//class ModelComponent;
+//class SpatialComponent;
+//class ShaderComponent;
+
+typedef entt::basic_view<unsigned int, entt::get_t<ModelComponent, SpatialComponent>, entt::exclude_t<ShaderComponent>> ModelSpatialView;
+typedef entt::basic_view<unsigned int, entt::get_t<ModelComponent, ShaderComponent, SpatialComponent>, entt::exclude_t<>> ModelShaderSpatialView;

+ 4 - 4
Praxis3D/Source/GUIDataManager.h

@@ -7,7 +7,7 @@
 class GUIDataManager
 {
 public:
-	GUIDataManager(const Observer &p_parent) : m_parent(p_parent)
+	GUIDataManager(const Observer &p_parent) : m_parent(&p_parent)
 	{
 		m_updateCount = 0;
 		m_changes = Systems::Changes::None;
@@ -30,7 +30,7 @@ public:
 		if(CheckBitmask(p_changeType, Systems::Changes::GUI::Sequence))
 		{
 			// Update functors
-			m_GUIData.m_functors = p_subject->getFunctors(&m_parent, Systems::Changes::GUI::Sequence);
+			m_GUIData.m_functors = p_subject->getFunctors(m_parent, Systems::Changes::GUI::Sequence);
 			m_changes |= Systems::Changes::GUI::Sequence;
 		}
 
@@ -80,8 +80,8 @@ private:
 	// GUI data
 	GUIData m_GUIData;
 
-	// A reference to the parent (which is the observer of all the received changes); required to retrieve the changed data from the observed subject
-	const Observer &m_parent;
+	// A pointer to the parent (which is the observer of all the received changes); required to retrieve the changed data from the observed subject
+	const Observer *m_parent;
 
 	// Used for update tracking; each time data is change, update count is incremented
 	UpdateCount m_updateCount;

+ 27 - 27
Praxis3D/Source/GUIObject.h

@@ -47,32 +47,32 @@ public:
 			if(!isLoadedToMemory())
 			{
 				// Check if there is a property set for sequence and load the GUI sequence component if there is
-				auto const &sequenceComponentProperty = p_properties.getPropertySetByID(Properties::Sequence);
-				if(sequenceComponentProperty)
-				{
-					// Create the GUI sequence component
-					addComponent(new GUISequenceComponent(m_systemScene, m_name + Config::componentVar().lua_component_name));
-
-					// Try to initialize the lua component
-					auto componentInitError = m_GUISequenceComponent->init();
-					if(componentInitError == ErrorCode::Success)
-					{
-						// Try to import the component
-						auto const &componentImportError = m_GUISequenceComponent->importObject(sequenceComponentProperty);
-
-						// Remove the component if it failed to import
-						if(componentImportError != ErrorCode::Success)
-						{
-							removeComponent(GUIComponentType::GUIComponentType_Sequence);
-							ErrHandlerLoc().get().log(componentImportError, ErrorSource::Source_GUISequenceComponent, m_name);
-						}
-					}
-					else // Remove the component if it failed to initialize
-					{
-						removeComponent(GUIComponentType::GUIComponentType_Sequence);
-						ErrHandlerLoc().get().log(componentInitError, ErrorSource::Source_GUISequenceComponent, m_name);
-					}
-				}
+				//auto const &sequenceComponentProperty = p_properties.getPropertySetByID(Properties::Sequence);
+				//if(sequenceComponentProperty)
+				//{
+				//	// Create the GUI sequence component
+				//	addComponent(new GUISequenceComponent(m_systemScene, m_name + Config::componentVar().lua_component_name));
+
+				//	// Try to initialize the lua component
+				//	auto componentInitError = m_GUISequenceComponent->init();
+				//	if(componentInitError == ErrorCode::Success)
+				//	{
+				//		// Try to import the component
+				//		auto const &componentImportError = m_GUISequenceComponent->importObject(sequenceComponentProperty);
+
+				//		// Remove the component if it failed to import
+				//		if(componentImportError != ErrorCode::Success)
+				//		{
+				//			removeComponent(GUIComponentType::GUIComponentType_Sequence);
+				//			ErrHandlerLoc().get().log(componentImportError, ErrorSource::Source_GUISequenceComponent, m_name);
+				//		}
+				//	}
+				//	else // Remove the component if it failed to initialize
+				//	{
+				//		removeComponent(GUIComponentType::GUIComponentType_Sequence);
+				//		ErrHandlerLoc().get().log(componentInitError, ErrorSource::Source_GUISequenceComponent, m_name);
+				//	}
+				//}
 			}
 		}
 		else
@@ -136,7 +136,7 @@ public:
 		m_GUISequenceComponent = p_component;
 
 		// Share the GUIObjects GUI data with the component
-		p_component->setGUIDataManagerReference(m_GUIData);
+		//p_component->setGUIDataManagerReference(m_GUIData);
 
 		// Set the flag for the GUI sequence component, so it is known from the flag that there is one currently present
 		m_componentsFlag |= Systems::GUIObjectComponents::Sequence;

+ 64 - 15
Praxis3D/Source/GUIScene.cpp

@@ -1,3 +1,5 @@
+
+#include "WorldScene.h"
 #include "GUIHandlerLocator.h"
 #include "GUIScene.h"
 #include "NullSystemObjects.h"
@@ -44,26 +46,27 @@ ErrorCode GUIScene::setup(const PropertySet& p_properties)
 
 void GUIScene::update(const float p_deltaTime)
 {
+	// Get the world scene required for getting components
+	WorldScene *worldScene = static_cast<WorldScene *>(m_sceneLoader->getSystemScene(Systems::World));
+
 	// Set the beginning of the GUI frame
 	GUIHandlerLocator::get().beginFrame();
 
-	for(decltype(m_GUIObjects.getPoolSize()) i = 0, numAllocObjecs = 0, totalNumAllocObjs = m_GUIObjects.getNumAllocated(),
-		size = m_GUIObjects.getPoolSize(); i < size && numAllocObjecs < totalNumAllocObjs; i++)
+	//	 ____________________________
+	//	|							 |
+	//	|	GUI SEQUENCE COMPONENTS	 |
+	//	|____________________________|
+	//
+	auto sequenceView = worldScene->getEntityRegistry().view<GUISequenceComponent>();
+	for(auto entity : sequenceView)
 	{
-		// Check if the GUI object is allocated inside the pool container
-		if(m_GUIObjects[i].allocated())
-		{
-			auto *currentGUIObject = m_GUIObjects[i].getObject();
+		GUISequenceComponent &sequenceComponent = sequenceView.get<GUISequenceComponent>(entity);
 
-			// Increment the number of allocated objects (early bail mechanism)
-			numAllocObjecs++;
-
-			// Check if the GUI object is enabled
-			if(currentGUIObject->isObjectActive())
-			{
-				// Update the object
-				currentGUIObject->update(p_deltaTime);
-			}
+		// Check if the script object is enabled
+		if(sequenceComponent.isObjectActive())
+		{
+			// Update the object
+			sequenceComponent.update(p_deltaTime);
 		}
 	}
 
@@ -90,6 +93,52 @@ void GUIScene::loadInBackground()
 {
 }
 
+SystemObject *GUIScene::createComponent(const EntityID &p_entityID, const std::string &p_entityName, const PropertySet &p_properties)
+{	
+	// If valid type was not specified, or object creation failed, return a null object instead
+	SystemObject *returnObject = g_nullSystemBase.getScene()->createObject(p_properties);
+
+	// Check if property set node is present
+	if(p_properties)
+	{
+		// Get the world scene required for attaching components to the entity
+		WorldScene *worldScene = static_cast<WorldScene*>(m_sceneLoader->getSystemScene(Systems::World));
+
+		switch(p_properties.getPropertyID())
+		{
+			case Properties::PropertyID::Sequence:
+			{
+				auto &component = worldScene->addComponent<GUISequenceComponent>(p_entityID, this, p_entityName + Config::componentVar().component_name_separator + GetString(Properties::PropertyID::Sequence), p_entityID);
+
+				// Try to initialize the component
+				auto componentInitError = component.init();
+				if(componentInitError == ErrorCode::Success)
+				{
+					// Try to import the component
+					auto const &componentImportError = component.importObject(p_properties);
+
+					// Remove the component if it failed to import
+					if(componentImportError != ErrorCode::Success)
+					{
+						ErrHandlerLoc().get().log(componentImportError, ErrorSource::Source_GUISequenceComponent, component.getName());
+						worldScene->removeComponent<GUISequenceComponent>(p_entityID);
+					}
+					else
+						returnObject = &component;
+				}
+				else // Remove the component if it failed to initialize
+				{
+					ErrHandlerLoc().get().log(componentInitError, ErrorSource::Source_GUISequenceComponent, component.getName());
+					worldScene->removeComponent<GUISequenceComponent>(p_entityID);
+				}
+			}
+			break;
+		}
+	}
+
+	return returnObject;
+}
+
 SystemObject* GUIScene::createObject(const PropertySet& p_properties)
 {
 	// Check if property set node is present

+ 1 - 0
Praxis3D/Source/GUIScene.h

@@ -26,6 +26,7 @@ public:
 	// Exports all the data of the scene (including all objects within) as a PropertySet (for example, used for saving to map file)
 	PropertySet exportObject() { return PropertySet(); }
 
+	SystemObject *createComponent(const EntityID &p_entityID, const std::string &p_entityName, const PropertySet &p_properties);
 	SystemObject *createObject(const PropertySet& p_properties);
 	ErrorCode destroyObject(SystemObject* p_systemObject);
 

+ 11 - 9
Praxis3D/Source/GUISequenceComponent.h

@@ -5,11 +5,11 @@
 #include "InheritanceObjects.h"
 #include "System.h"
 
-class GUISequenceComponent : public SystemObject, public GUIDataManagerObject, public LoadableGraphicsObject
+class GUISequenceComponent : public SystemObject
 {
 	friend class ScriptScene;
 public:
-	GUISequenceComponent(SystemScene *p_systemScene, std::string p_name, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::GUISequenceComponent)
+	GUISequenceComponent(SystemScene *p_systemScene, std::string p_name, const EntityID p_entityID, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::GUISequenceComponent, p_entityID)//, m_GUIData(*this)
 	{
 		m_staticSequence = false;
 	}
@@ -41,7 +41,7 @@ public:
 		ErrorCode importError = ErrorCode::Failure;
 
 		// Check if PropertySet isn't empty and the component hasn't been loaded already
-		if(p_properties && !isLoadedToMemory())
+		if(p_properties)// && !isLoadedToMemory())
 		{
 			if(p_properties.getPropertyID() == Properties::Sequence)
 			{
@@ -82,8 +82,8 @@ public:
 
 			if(importError == ErrorCode::Success)
 			{
-				setLoadedToMemory(true);
-				setLoadedToVideoMemory(true);
+				//setLoadedToMemory(true);
+				//setLoadedToVideoMemory(true);
 				setActive(true);
 			}
 		}
@@ -99,16 +99,16 @@ public:
 		return propertySet;
 	}
 
-	// System type is Graphics
+	// System type is GUI
 	BitMask getSystemType() final override { return Systems::GUI; }
 
-	BitMask getDesiredSystemChanges() final override { return Systems::Changes::None; }
-	BitMask getPotentialSystemChanges() final override { return Systems::Changes::GUI::All; }
+	BitMask getDesiredSystemChanges() final override { return Systems::Changes::GUI::All; }
+	BitMask getPotentialSystemChanges() final override { return Systems::Changes::None; }
 
 	const Functors &getFunctors(const Observer *p_observer, BitMask p_changedBits)
 	{
 		if(CheckBitmask(p_changedBits, Systems::Changes::Type::GUI))
-				return m_guiSequence;
+			return m_guiSequence;
 	}
 
 	void changeOccurred(ObservedSubject *p_subject, BitMask p_changeType) 
@@ -118,6 +118,8 @@ public:
 	}
 
 private:
+	// GUI data
+	//GUIDataManager m_GUIData;
 	Functors m_guiSequence;
 
 	bool m_staticSequence;

+ 3 - 3
Praxis3D/Source/GameObject.h

@@ -17,7 +17,7 @@ class GameObject : public SystemObject
 {
 	friend class WorldScene;
 public:
-	GameObject(SystemScene *p_systemScene, std::string p_name, SceneLoader &p_sceneLoader, ObjectID p_gmaeObjectID = 0) : SystemObject(p_systemScene, p_name, Properties::GameObject), m_sceneLoader(p_sceneLoader), m_GameObjectID(p_gmaeObjectID), m_spatialData(*this)
+	GameObject(SystemScene *p_systemScene, std::string p_name, SceneLoader &p_sceneLoader, EntityID p_gmaeObjectID = 0) : SystemObject(p_systemScene, p_name, Properties::GameObject), m_sceneLoader(p_sceneLoader), m_GameObjectID(p_gmaeObjectID), m_spatialData(*this)
 	{
 		m_parent = nullptr;
 		m_graphicsComponent = nullptr;
@@ -122,7 +122,7 @@ public:
 		// Add the child to the children array
 		m_children.push_back(&p_child); 
 	}
-	void removeChild(const std::size_t p_id)
+	void removeChild(const EntityID p_id)
 	{
 		// Loop over every child
 		for(decltype(m_children.size()) size = m_children.size(), i = 0; i < size; i++)
@@ -340,5 +340,5 @@ private:
 	SpatialDataManager m_spatialData;
 
 	// ID of a GameObject; separate from m_objectID of a SystemObject
-	ObjectID m_GameObjectID;
+	EntityID m_GameObjectID;
 };

+ 115 - 0
Praxis3D/Source/GameObjectComponent.h

@@ -0,0 +1,115 @@
+#pragma once
+
+//#include "InheritanceObjects.h"
+//#include "SpatialDataManager.h"
+//#include "SceneLoader.h"
+#include "System.h"
+
+// A component containing data on an entity itself (i.e. a game object), like game object name, hierarchy (parent and children objects)
+class GameObjectComponent : public SystemObject
+{
+	friend class WorldScene;
+public:
+	GameObjectComponent(SystemScene *p_systemScene, const std::string &p_name, EntityID p_entityID)
+		: SystemObject(p_systemScene, p_name, Properties::SpatialComponent), m_entityID(p_entityID)
+	{
+		m_parent = 0;
+	}
+	~GameObjectComponent()
+	{
+		// Iterate over all component types and delete components if they have been created
+		//for(std::size_t i = 0; i < ScriptComponentType::ScriptComponentType_NumOfComponents; i++)
+		//	removeComponent(static_cast<ScriptComponentType>(i));
+	}
+
+	ErrorCode init() { return ErrorCode::Success; }
+
+	void loadToMemory()
+	{
+		setActive(true);
+	}
+
+	// System type is World
+	BitMask getSystemType() { return Systems::World; }
+
+	void update(const float p_deltaTime)
+	{
+
+	}
+
+	// Get the data change types that this object is interested in
+	BitMask getDesiredSystemChanges() override { return Systems::Changes::Generic::Name; }
+
+	// Get the data change types that this object might modify
+	BitMask getPotentialSystemChanges() override { return Systems::Changes::None; }
+
+	void changeOccurred(ObservedSubject *p_subject, BitMask p_changeType)
+	{
+		// Track what data has been modified
+		BitMask newChanges = Systems::Changes::None;
+
+		// Process the spatial changes and record the world-space changes
+		//newChanges = m_spatialData.changeOccurred(*p_subject, p_changeType & Systems::Changes::Spatial::All);
+
+		// If any data has been updated, post the changes to listeners
+		if(newChanges != Systems::Changes::None)
+		{
+			//postChanges(newChanges);
+		}
+	}
+
+	ErrorCode importObject(const PropertySet &p_properties)
+	{
+		ErrorCode returnError = ErrorCode::Success;
+
+		// Check if the property set is valid
+		if(p_properties)
+		{
+			// Load property data
+			for(decltype(p_properties.getNumProperties()) i = 0, size = p_properties.getNumProperties(); i < size; i++)
+			{
+				switch(p_properties[i].getPropertyID())
+				{
+
+				case Properties::LocalPosition:
+					//m_spatialData.setLocalPosition(p_properties[i].getVec3f());
+					break;
+				case Properties::LocalRotation:
+					//m_spatialData.setLocalRotation(p_properties[i].getVec3f());
+					break;
+				case Properties::LocalRotationQuaternion:
+					//m_spatialData.setLocalRotation(glm::quat(p_properties[i].getVec4f()));
+					break;
+				case Properties::LocalScale:
+					//m_spatialData.setLocalScale(p_properties[i].getVec3f());
+					break;
+				}
+			}
+		}
+		else
+		{
+			returnError = ErrorCode::Failure;
+		}
+
+		return returnError;
+	}
+
+	PropertySet exportObject()
+	{
+		return PropertySet();
+	}
+
+	inline void setParent(const EntityID p_parent) { m_parent = p_parent; }
+	inline void addChild(const EntityID p_child) { m_children.push_back(p_child); }
+
+	inline void clearChildren() { m_children.clear(); }
+	inline std::vector<EntityID> &getChildren() { return m_children; }
+
+private:
+	EntityID m_entityID;
+
+	// Parent and children hierarchy
+	std::vector<EntityID> m_children;
+	EntityID m_parent;
+};
+

+ 48 - 3
Praxis3D/Source/GeometryPass.h

@@ -70,6 +70,51 @@ public:
 		auto &geomUniformUpdater = m_shaderGeometry->getUniformUpdater();
 
 		// Iterate over all objects to be rendered with geometry shader
+		for(auto entity : p_sceneObjects.m_models)
+		{
+			ModelComponent &model = p_sceneObjects.m_models.get<ModelComponent>(entity);
+			if(model.isObjectActive() && model.isLoadedToVideoMemory())
+			{
+				SpatialComponent &spatialData = p_sceneObjects.m_models.get<SpatialComponent>(entity);
+				auto &modelData = model.getModelData();
+
+				for(decltype(modelData.size()) i = 0, size = modelData.size(); i < size; i++)
+				{
+					m_renderer.queueForDrawing(modelData[i],
+						geomShaderHandle,
+						geomUniformUpdater,
+						spatialData.getSpatialDataChangeManager().getWorldTransform(),
+						m_renderer.m_viewProjMatrix);
+				}
+			}
+		}
+
+		// Iterate over all objects to be rendered with a custom shader
+		for(auto entity : p_sceneObjects.m_modelsWithShaders)
+		{
+			ModelComponent &model = p_sceneObjects.m_modelsWithShaders.get<ModelComponent>(entity);
+			if(model.isObjectActive() && model.isLoadedToVideoMemory())
+			{
+				SpatialComponent &spatialData = p_sceneObjects.m_modelsWithShaders.get<SpatialComponent>(entity);
+				ShaderComponent &shader = p_sceneObjects.m_modelsWithShaders.get<ShaderComponent>(entity);
+				if(shader.isLoadedToVideoMemory())
+				{
+					auto &modelData = model.getModelData();
+
+					for(decltype(modelData.size()) i = 0, size = modelData.size(); i < size; i++)
+					{
+						m_renderer.queueForDrawing(modelData[i],
+							shader.getShaderData()->m_shader.getShaderHandle(),
+							shader.getShaderData()->m_shader.getUniformUpdater(),
+							spatialData.getSpatialDataChangeManager().getWorldTransform(),
+							m_renderer.m_viewProjMatrix);
+					}
+				}
+			}
+		}
+
+
+		/*/ Iterate over all objects to be rendered with geometry shader
 		for(decltype(p_sceneObjects.m_modelData.size()) objIndex = 0, numObjects = p_sceneObjects.m_modelData.size(); objIndex < numObjects; objIndex++)
 		{
 			m_renderer.queueForDrawing(p_sceneObjects.m_modelData[objIndex].m_modelData,
@@ -77,9 +122,9 @@ public:
 								geomUniformUpdater,
 								p_sceneObjects.m_modelData[objIndex].m_modelMatrix,
 								m_renderer.m_viewProjMatrix);
-		}
+		}*/
 
-		// Iterate over all objects to be rendered with a custom shader
+		/*/ Iterate over all objects to be rendered with a custom shader
 		for(decltype(p_sceneObjects.m_modelDataWithShaders.size()) objIndex = 0, numObjects = p_sceneObjects.m_modelDataWithShaders.size(); objIndex < numObjects; objIndex++)
 		{
 			m_renderer.queueForDrawing(p_sceneObjects.m_modelDataWithShaders[objIndex].m_modelData,
@@ -87,7 +132,7 @@ public:
 				p_sceneObjects.m_modelDataWithShaders[objIndex].m_shader->getUniformUpdater(),
 				p_sceneObjects.m_modelDataWithShaders[objIndex].m_modelMatrix,
 				m_renderer.m_viewProjMatrix);
-		}
+		}*/
 
 		// Pass all the draw commands to be executed
 		m_renderer.passDrawCommandsToBackend();

+ 2 - 2
Praxis3D/Source/GraphicsDataSets.h

@@ -27,7 +27,7 @@ struct MaterialData
 struct MeshData
 {
 	MeshData(const Model::Mesh &p_mesh, MaterialData p_materials[MaterialType::MaterialType_NumOfTypes], const float p_heightScale, const float p_alphaThreshold) : 
-		m_mesh(p_mesh), 
+		m_mesh(&p_mesh), 
 		m_heightScale(p_heightScale),
 		m_alphaThreshold(p_alphaThreshold) 
 	{
@@ -40,7 +40,7 @@ struct MeshData
 	float m_alphaThreshold;
 
 	// Handle to a mesh
-	const Model::Mesh &m_mesh;
+	const Model::Mesh *m_mesh;
 
 	// An array of materials of each type
 	MaterialData m_materials[MaterialType::MaterialType_NumOfTypes];

+ 10 - 10
Praxis3D/Source/GraphicsObject.h

@@ -70,7 +70,7 @@ public:
 				if(camera)
 				{
 					// Create the camera component
-					addComponent(new CameraComponent(m_systemScene, m_name + Config::componentVar().camera_component_name));
+					//addComponent(new CameraComponent(m_systemScene, m_name + Config::componentVar().camera_component_name));
 
 					// Try to initialize the camera component
 					auto componentInitError = m_cameraComponent->init();
@@ -98,7 +98,7 @@ public:
 				if(lighting)
 				{
 					// Create the light component
-					addComponent(new LightComponent(m_systemScene, m_name + Config::componentVar().light_component_name));
+					//addComponent(new LightComponent(m_systemScene, m_name + Config::componentVar().light_component_name));
 
 					// Try to initialize the light component
 					auto componentInitError = m_lightComponent->init();
@@ -126,7 +126,7 @@ public:
 				if(models)
 				{
 					// Create the model component
-					addComponent(new ModelComponent(m_systemScene, m_name + Config::componentVar().model_component_name));
+					//addComponent(new ModelComponent(m_systemScene, m_name + Config::componentVar().model_component_name));
 
 					// Try to initialize the model component
 					auto componentInitError = m_modelComponent->init();
@@ -144,7 +144,7 @@ public:
 					}
 					else // Remove the component if it failed to initialize
 					{
-						removeComponent(GraphicsComponentType::GraphicsComponentType_Camera);
+						removeComponent(GraphicsComponentType::GraphicsComponentType_Model);
 						ErrHandlerLoc().get().log(componentInitError, ErrorSource::Source_ModelComponent, m_name);
 					}
 				}
@@ -154,7 +154,7 @@ public:
 				if(shaders)
 				{
 					// Create the shader component
-					addComponent(new ShaderComponent(m_systemScene, m_name + Config::componentVar().shader_component_name));
+					//addComponent(new ShaderComponent(m_systemScene, m_name + Config::componentVar().shader_component_name));
 
 					// Try to initialize the shader component
 					auto componentInitError = m_shaderComponent->init();
@@ -172,7 +172,7 @@ public:
 					}
 					else // Remove the component if it failed to initialize
 					{
-						removeComponent(GraphicsComponentType::GraphicsComponentType_Camera);
+						removeComponent(GraphicsComponentType::GraphicsComponentType_Shader);
 						ErrHandlerLoc().get().log(componentInitError, ErrorSource::Source_ShaderComponent, m_name);
 					}
 				}
@@ -253,8 +253,8 @@ public:
 	{
 		SpatialDataManagerObject::setSpatialDataManagerReference(p_spatialData);
 
-		if(lightComponentPresent())
-			m_lightComponent->setSpatialDataManagerReference(*m_spatialData);
+		//if(lightComponentPresent())
+		//	m_lightComponent->setSpatialDataManagerReference(*m_spatialData);
 	}
 
 	// System type is Graphics
@@ -339,7 +339,7 @@ public:
 		m_cameraComponent = p_component;
 
 		// Share the GraphicsObjects spatial data with the component
-		m_cameraComponent->setSpatialDataManagerReference(*m_spatialData);
+		//m_cameraComponent->setSpatialDataManagerReference(*m_spatialData);
 
 		// Set the flag for the camera component, so it is known from the flag that there is one currently present
 		m_componentsFlag |= Systems::GraphicsObjectComponents::Camera;
@@ -351,7 +351,7 @@ public:
 		m_lightComponent = p_component;
 
 		// Share the GraphicsObjects spatial data with the component
-		m_lightComponent->setSpatialDataManagerReference(*m_spatialData);
+		//m_lightComponent->setSpatialDataManagerReference(*m_spatialData);
 
 		// Set the flag for the lighting component, so it is known from the flag that there is one currently present
 		m_componentsFlag |= Systems::GraphicsObjectComponents::Lighting;

+ 13 - 0
Praxis3D/Source/InheritanceObjects.h

@@ -192,4 +192,17 @@ public:
 protected:
 	bool	m_loadedToMemory,
 			m_loadedToVideoMemory;
+};
+
+// Functionality for tracking an index of a double buffer
+class DoubleBufferObject
+{
+public:
+	DoubleBufferObject() : m_bufferIndex(0) { }
+	~DoubleBufferObject() { }
+
+	void swapBuffers() { m_bufferIndex = !m_bufferIndex; }
+
+protected:
+	std::size_t m_bufferIndex;
 };

+ 23 - 20
Praxis3D/Source/LightComponent.h

@@ -6,7 +6,7 @@
 #include "ObserverBase.h"
 #include "System.h"
 
-class LightComponent : public SystemObject, public SpatialDataManagerObject, public LoadableGraphicsObject
+class LightComponent : public SystemObject, public LoadableGraphicsObject
 {
 	friend class RendererScene;
 public:
@@ -18,21 +18,21 @@ public:
 		LightComponentType_spot
 	};
 
-	LightComponent(SystemScene *p_systemScene, std::string p_name, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::Lighting)
+	LightComponent(SystemScene *p_systemScene, std::string p_name, const EntityID p_entityID, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::Lighting, p_entityID)
 	{
 		m_lightComponentType = LightComponentType::LightComponentType_null;
 	}
-	LightComponent(SystemScene *p_systemScene, std::string p_name, DirectionalLightDataSet &p_lightDataSet, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::DirectionalLight)
+	LightComponent(SystemScene *p_systemScene, std::string p_name, DirectionalLightDataSet &p_lightDataSet, const EntityID p_entityID, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::DirectionalLight, p_entityID)
 	{
 		m_lightComponentType = LightComponentType::LightComponentType_directional;
 		m_lightComponent.m_directional = p_lightDataSet;
 	}
-	LightComponent(SystemScene *p_systemScene, std::string p_name, PointLightDataSet &p_lightDataSet, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::PointLight)
+	LightComponent(SystemScene *p_systemScene, std::string p_name, PointLightDataSet &p_lightDataSet, const EntityID p_entityID, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::PointLight, p_entityID)
 	{
 		m_lightComponentType = LightComponentType::LightComponentType_point;
 		m_lightComponent.m_point = p_lightDataSet;
 	}
-	LightComponent(SystemScene *p_systemScene, std::string p_name, SpotLightDataSet &p_lightDataSet, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::SpotLight)
+	LightComponent(SystemScene *p_systemScene, std::string p_name, SpotLightDataSet &p_lightDataSet, const EntityID p_entityID, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::SpotLight, p_entityID)
 	{
 		m_lightComponentType = LightComponentType::LightComponentType_spot;
 		m_lightComponent.m_spot = p_lightDataSet;
@@ -66,7 +66,7 @@ public:
 		ErrorCode importError = ErrorCode::Failure;
 
 		// Check if light node is present and the component hasn't been loaded already
-		if(p_properties && !isLoadedToMemory())
+		if(p_properties && !isLoadedToMemory()) 
 		{	
 			// Get the light type
 			auto const &type = p_properties.getPropertyByID(Properties::Type).getID();
@@ -80,7 +80,6 @@ public:
 
 				m_lightComponent.m_directional.m_color = p_properties.getPropertyByID(Properties::Color).getVec3f();
 				m_lightComponent.m_directional.m_intensity = p_properties.getPropertyByID(Properties::Intensity).getFloat();
-				m_lightComponent.m_directional.m_direction = glm::vec3(m_spatialData->getWorldTransform()[2]);
 				setLoadedToMemory(true);
 				importError = ErrorCode::Success;
 
@@ -93,7 +92,6 @@ public:
 
 				m_lightComponent.m_point.m_color = p_properties.getPropertyByID(Properties::Color).getVec3f();
 				m_lightComponent.m_point.m_intensity = p_properties.getPropertyByID(Properties::Intensity).getFloat();
-				m_lightComponent.m_point.m_position = glm::vec3(m_spatialData->getWorldTransform()[3]);
 				setLoadedToMemory(true);
 				importError = ErrorCode::Success;
 
@@ -107,8 +105,6 @@ public:
 				m_lightComponent.m_spot.m_color = p_properties.getPropertyByID(Properties::Color).getVec3f();
 				m_lightComponent.m_spot.m_cutoffAngle = p_properties.getPropertyByID(Properties::CutoffAngle).getFloat();
 				m_lightComponent.m_spot.m_intensity = p_properties.getPropertyByID(Properties::Intensity).getFloat();
-				m_lightComponent.m_spot.m_direction = glm::vec3(m_spatialData->getWorldTransform()[2]);
-				m_lightComponent.m_spot.m_position = glm::vec3(m_spatialData->getWorldTransform()[3]);
 				setLoadedToMemory(true);
 				importError = ErrorCode::Success;
 
@@ -172,8 +168,8 @@ public:
 
 	void loadToMemory() 
 	{
-		updatePosition(glm::vec3(m_spatialData->getWorldTransform()[3]));
-		updateRotation(glm::vec3(m_spatialData->getWorldTransform()[2]));
+		//updatePosition(glm::vec3(m_spatialData->getWorldTransform()[3]));
+		//updateRotation(glm::vec3(m_spatialData->getWorldTransform()[2]));
 	}
 
 	// System type is Graphics
@@ -182,11 +178,11 @@ public:
 	void update(const float p_deltaTime)
 	{
 		// If the spatial data has changed, update the spatial data in light datasets
-		if(hasSpatialDataUpdated())
-		{
-			updatePosition(glm::vec3(m_spatialData->getWorldTransform()[3]));
-			updateRotation(glm::vec3(m_spatialData->getWorldTransform()[2]));
-		}
+		//if(hasSpatialDataUpdated())
+		//{
+		//	updatePosition(glm::vec3(m_spatialData->getWorldTransform()[3]));
+		//	updateRotation(glm::vec3(m_spatialData->getWorldTransform()[2]));
+		//}
 
 		if(isUpdateNeeded())
 		{
@@ -256,12 +252,19 @@ public:
 
 	inline const LightComponentType getLightType() const { return m_lightComponentType; }
 
+	// Get the directional light data set. Unsafe - does not perform a check whether the light type is correct
+	inline DirectionalLightDataSet *getDirectionalLight() { return &m_lightComponent.m_directional; }
+	// Get the point light data set. Unsafe - does not perform a check whether the light type is correct
+	inline PointLightDataSet *getPointLight() { return &m_lightComponent.m_point; }
+	// Get the spot light data set. Unsafe - does not perform a check whether the light type is correct
+	inline SpotLightDataSet *getSpotLight() { return &m_lightComponent.m_spot; }
+
 	// Get the directional light data set. If the type of light is not directional, returns a null pointer
-	DirectionalLightDataSet *getDirectionalLight() { return (m_lightComponentType == LightComponentType::LightComponentType_directional) ? &m_lightComponent.m_directional : nullptr; }
+	inline DirectionalLightDataSet *getDirectionalLightSafe() { return (m_lightComponentType == LightComponentType::LightComponentType_directional) ? &m_lightComponent.m_directional : nullptr; }
 	// Get the point light data set. If the type of light is not point, returns a null pointer
-	PointLightDataSet *getPointLight() { return (m_lightComponentType == LightComponentType::LightComponentType_point) ? &m_lightComponent.m_point : nullptr; }
+	inline PointLightDataSet *getPointLightSafe() { return (m_lightComponentType == LightComponentType::LightComponentType_point) ? &m_lightComponent.m_point : nullptr; }
 	// Get the spot light data set. If the type of light is not spot, returns a null pointer
-	SpotLightDataSet *getSpotLight() { return (m_lightComponentType == LightComponentType::LightComponentType_spot) ? &m_lightComponent.m_spot : nullptr; }
+	inline SpotLightDataSet *getSpotLightSafe() { return (m_lightComponentType == LightComponentType::LightComponentType_spot) ? &m_lightComponent.m_spot : nullptr; }
 
 private:
 	// Union object that hold any one of the three light types

+ 10 - 0
Praxis3D/Source/LoaderBase.h

@@ -136,6 +136,9 @@ public:
 		});
 	}
 
+	// Returns false if there are any objects in the LoadToVideoMemory queue
+	//const inline bool isLoadToVideoMemoryQueueEmpty() const { return m_objectLoadToVideoMemoryQueue.empty(); }
+
 protected:
 	// Queue and object to be removed from memory
 	inline void queueUnload(UniqueObject &p_object)
@@ -144,6 +147,12 @@ protected:
 		m_queueIsEmpty = false;
 	}
 
+	// Put an object into a queue for loading it into video memory
+	//inline void queueLoadToVideoMemory(TObject &p_object)
+	//{
+	//	m_objectLoadToVideoMemoryQueue.push(&p_object);
+	//}
+
 	// Swap the object with the last element of vector and pop_back
 	inline void removeObject(UniqueObject &p_object)
 	{
@@ -173,4 +182,5 @@ private:
 	bool m_queueIsEmpty;
 	
 	std::queue<UniqueObject*> m_objectUnloadQueue;
+	//std::queue<TObject*> m_objectLoadToVideoMemoryQueue;
 };

+ 22 - 12
Praxis3D/Source/LuaComponent.h

@@ -6,18 +6,23 @@
 #include "Filesystem.h"
 #include "InheritanceObjects.h"
 #include "LuaScript.h"
+#include "SpatialComponent.h"
 #include "System.h"
 
 class LuaComponent : public SystemObject, public SpatialDataManagerObject, public LoadableGraphicsObject
 {
 	friend class ScriptScene;
 public:
-	LuaComponent(SystemScene *p_systemScene, std::string p_name, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::LuaComponent), m_luaSpatialData(*this), m_GUIData(*this), m_luaScript(m_luaSpatialData, m_GUIData)
+	LuaComponent(SystemScene *p_systemScene, std::string p_name, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::LuaComponent), m_luaSpatialData(*this), m_GUIData(*this)
 	{
+		m_luaScript = new LuaScript(m_luaSpatialData, m_GUIData);
 		m_luaScriptLoaded = false;
 		m_luaSpatialData.setTrackLocalChanges(true);
 	}
-	~LuaComponent() { }
+	~LuaComponent() 
+	{
+		delete m_luaScript;
+	}
 
 	ErrorCode init() final override
 	{
@@ -30,7 +35,7 @@ public:
 
 	void loadToMemory()
 	{
-		auto luaError = m_luaScript.init();
+		auto luaError = m_luaScript->init();
 
 		if(luaError != ErrorCode::Success)
 			ErrHandlerLoc().get().log(luaError, ErrorSource::Source_LuaComponent, m_name);
@@ -45,20 +50,25 @@ public:
 		// Perform updates only the if the script is loaded
 		if(m_luaScriptLoaded)
 		{
-			// Get the current spatial data
-			m_luaSpatialData.setSpatialData(*m_spatialData);
-
 			// Update the lua script
-			m_luaScript.update(p_deltaTime);
+			m_luaScript->update(p_deltaTime);
 
 			// Get the changes from the lua script
-			auto changes = m_luaScript.getChanges();
+			auto changes = m_luaScript->getChanges();
 
 			// Post the new changes
 			postChanges(changes);
 		}
 	}
 
+	inline void update(const float p_deltaTime, const SpatialComponent &p_spatialComponent)
+	{
+		// Get the current spatial data
+		m_luaSpatialData.setSpatialData(p_spatialComponent.getSpatialDataChangeManager());
+
+		update(p_deltaTime);
+	}
+
 	ErrorCode importObject(const PropertySet &p_properties) final override
 	{
 		ErrorCode importError = ErrorCode::Failure;
@@ -66,7 +76,7 @@ public:
 		// Check if PropertySet isn't empty and the component hasn't been loaded already
 		if(p_properties && !isLoadedToMemory())
 		{
-			if(p_properties.getPropertyID() == Properties::Lua)
+			if(p_properties.getPropertyID() == Properties::LuaComponent)
 			{
 				auto const &luaFilenameProperty = p_properties.getPropertyByID(Properties::Filename);
 				auto const &luaVariablesProperty = p_properties.getPropertySetByID(Properties::Variables);
@@ -79,10 +89,10 @@ public:
 						luaFilename = Config::filepathVar().script_path + luaFilename;
 						if(Filesystem::exists(luaFilename))
 						{
-							m_luaScript.setScriptFilename(luaFilename);
+							m_luaScript->setScriptFilename(luaFilename);
 
 							if(luaVariablesProperty)
-								m_luaScript.setVariables(luaVariablesProperty);
+								m_luaScript->setVariables(luaVariablesProperty);
 
 							importError = ErrorCode::Success;
 							ErrHandlerLoc().get().log(ErrorType::Info, ErrorSource::Source_LuaComponent, m_name + " - Script loaded");
@@ -184,7 +194,7 @@ public:
 	}
 
 private:
-	LuaScript m_luaScript;
+	LuaScript *m_luaScript;
 	bool m_luaScriptLoaded;
 
 	SpatialDataManager m_luaSpatialData;

+ 0 - 1
Praxis3D/Source/LuaScript.h

@@ -573,7 +573,6 @@ private:
 	// Lua state for the loaded script
 	sol::state m_luaState;
 	std::string m_luaScriptFilename;
-
 	// Function binds that call functions inside the lua script
 	sol::function m_luaInit;
 	sol::function m_luaUpdate;

+ 151 - 144
Praxis3D/Source/ModelComponent.h

@@ -8,7 +8,7 @@ class ModelComponent : public SystemObject, public LoadableGraphicsObject
 {
 	friend class RendererScene;
 public:
-	ModelComponent(SystemScene *p_systemScene, std::string p_name, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::Models)
+	ModelComponent(SystemScene *p_systemScene, std::string p_name, const EntityID p_entityID, std::size_t p_id = 0) : SystemObject(p_systemScene, p_name, Properties::PropertyID::Models, p_entityID)
 	{
 		m_materialsFromProperties = nullptr;
 		m_modelsProperties = nullptr;
@@ -27,7 +27,7 @@ public:
 				m_modelData.emplace_back(Loaders::model().load(m_modelsProperties->m_modelNames[modelIndex], false));
 				auto &newModelData = m_modelData.back();
 
-				// Load the model to memory, to be able to access all of its meshes
+				// Load the model to memory, to be able to access all of its meshes 
 				auto modelLoadError = newModelData.m_model.loadToMemory();
 
 				if(modelLoadError == ErrorCode::Success)
@@ -121,192 +121,197 @@ public:
 		// Check if models node is present and the component hasn't been loaded already
 		if(p_properties && !isLoadedToMemory())
 		{
-			importError = ErrorCode::Success;
+			auto &modelsProperty = p_properties.getPropertySetByID(Properties::Models);
 
-			if(m_modelsProperties != nullptr)
-				delete m_modelsProperties;
+			if(modelsProperty)
+			{
+				importError = ErrorCode::Success;
 
-			m_modelsProperties = new ModelsProperties();
+				if(m_modelsProperties != nullptr)
+					delete m_modelsProperties;
 
-			// Loop over each model entry in the node
-			for(decltype(p_properties.getNumPropertySets()) iModel = 0, numModels = p_properties.getNumPropertySets(); iModel < numModels; iModel++)
-			{
-				// Get model filename
-				auto modelName = p_properties.getPropertySet(iModel).getPropertyByID(Properties::Filename).getString();
+				m_modelsProperties = new ModelsProperties();
 
-				// Add a new model data entry, and get a reference to it
-				//m_modelData.emplace_back(Loaders::model().load(modelName, false));
-				m_modelsProperties->m_modelNames.push_back(modelName);
-				//auto &newModelData = m_modelData.back();
+				// Loop over each model entry in the node
+				for(decltype(modelsProperty.getNumPropertySets()) iModel = 0, numModels = modelsProperty.getNumPropertySets(); iModel < numModels; iModel++)
+				{
+					// Get model filename
+					auto modelName = modelsProperty.getPropertySet(iModel).getPropertyByID(Properties::Filename).getString();
 
-				// Load the model to memory, to be able to access all of its meshes
-				//auto modelLoadError = newModelData.m_model.loadToMemory();
+					// Add a new model data entry, and get a reference to it
+					//m_modelData.emplace_back(Loaders::model().load(modelName, false));
+					m_modelsProperties->m_modelNames.push_back(modelName);
+					//auto &newModelData = m_modelData.back();
 
-				//if(modelLoadError == ErrorCode::Success)
-				//{
-					// Set the component as not being empty anymore, since a model has been loaded successfully
-					//setEmpty(false);
+					// Load the model to memory, to be able to access all of its meshes
+					//auto modelLoadError = newModelData.m_model.loadToMemory();
 
-					// Get the meshes array
-					//const std::vector<Model::Mesh> &meshesInModelArray = newModelData.m_model.getMeshArray();
+					//if(modelLoadError == ErrorCode::Success)
+					//{
+						// Set the component as not being empty anymore, since a model has been loaded successfully
+						//setEmpty(false);
 
-					// Get the meshes array
-				auto &meshesProperty = p_properties.getPropertySet(iModel).getPropertySetByID(Properties::Meshes);
+						// Get the meshes array
+						//const std::vector<Model::Mesh> &meshesInModelArray = newModelData.m_model.getMeshArray();
 
-				// Check if the meshes array node is present;
-				// If it is present, only add the meshes included in the meshes node
-				// If it is not present, add all the meshes included in the model
-				if(meshesProperty)
-				{
-					if (meshesProperty.getNumPropertySets() > 0)
-					{
-						m_materialsFromProperties = new MeshMaterialsProperties();
+						// Get the meshes array
+					auto &meshesProperty = modelsProperty.getPropertySet(iModel).getPropertySetByID(Properties::Meshes);
 
-						// Loop over each mesh entry in the model node
-						for (decltype(meshesProperty.getNumPropertySets()) iMesh = 0, numMeshes = meshesProperty.getNumPropertySets(); iMesh < numMeshes; iMesh++)
+					// Check if the meshes array node is present;
+					// If it is present, only add the meshes included in the meshes node
+					// If it is not present, add all the meshes included in the model
+					if(meshesProperty)
+					{
+						if(meshesProperty.getNumPropertySets() > 0)
 						{
-							// Try to get the mesh index property node and check if it is present
-							auto& meshIndexProperty = meshesProperty.getPropertySet(iMesh).getPropertyByID(Properties::Index);
-							if(meshIndexProperty)
-							{
-								// Get the mesh index, check if it is valid and within the range of mesh array that was loaded from the model
-								const int meshDataIndex = meshIndexProperty.getInt();
+							m_materialsFromProperties = new MeshMaterialsProperties();
 
-								// Make sure the meshMaterials vector can fit the given mesh index
-								if(meshDataIndex >= m_materialsFromProperties->m_meshMaterials.size())
+							// Loop over each mesh entry in the model node
+							for(decltype(meshesProperty.getNumPropertySets()) iMesh = 0, numMeshes = meshesProperty.getNumPropertySets(); iMesh < numMeshes; iMesh++)
+							{
+								// Try to get the mesh index property node and check if it is present
+								auto &meshIndexProperty = meshesProperty.getPropertySet(iMesh).getPropertyByID(Properties::Index);
+								if(meshIndexProperty)
 								{
-									m_materialsFromProperties->resize(meshDataIndex + 1);
-									m_materialsFromProperties->m_present[meshDataIndex] = true;
-								}
+									// Get the mesh index, check if it is valid and within the range of mesh array that was loaded from the model
+									const int meshDataIndex = meshIndexProperty.getInt();
 
-								// Get material alpha threshold value, if it is present
-								auto alphaThresholdProperty = meshesProperty.getPropertySet(iMesh).getPropertyByID(Properties::AlphaThreshold);
-								if(alphaThresholdProperty)
-									m_materialsFromProperties->m_alphaThreshold[meshDataIndex] = alphaThresholdProperty.getFloat();
+									// Make sure the meshMaterials vector can fit the given mesh index
+									if(meshDataIndex >= m_materialsFromProperties->m_meshMaterials.size())
+									{
+										m_materialsFromProperties->resize(meshDataIndex + 1);
+										m_materialsFromProperties->m_present[meshDataIndex] = true;
+									}
 
-								// Get material height scale value, if it is present
-								auto heightScaleProperty = meshesProperty.getPropertySet(iMesh).getPropertyByID(Properties::HeightScale);
-								if(heightScaleProperty)
-									m_materialsFromProperties->m_heightScale[meshDataIndex] = heightScaleProperty.getFloat();
+									// Get material alpha threshold value, if it is present
+									auto alphaThresholdProperty = meshesProperty.getPropertySet(iMesh).getPropertyByID(Properties::AlphaThreshold);
+									if(alphaThresholdProperty)
+										m_materialsFromProperties->m_alphaThreshold[meshDataIndex] = alphaThresholdProperty.getFloat();
 
-								// Get material properties
-								auto materialsProperty = meshesProperty.getPropertySet(iMesh).getPropertySetByID(Properties::Materials);
+									// Get material height scale value, if it is present
+									auto heightScaleProperty = meshesProperty.getPropertySet(iMesh).getPropertyByID(Properties::HeightScale);
+									if(heightScaleProperty)
+										m_materialsFromProperties->m_heightScale[meshDataIndex] = heightScaleProperty.getFloat();
 
-								// Define material data and material properties
-								MaterialData materials[MaterialType::MaterialType_NumOfTypes];
-								PropertySet materialProperties[MaterialType::MaterialType_NumOfTypes] =
-								{
-									materialsProperty.getPropertySetByID(Properties::Diffuse),
-									materialsProperty.getPropertySetByID(Properties::Normal),
-									materialsProperty.getPropertySetByID(Properties::Emissive),
-									materialsProperty.getPropertySetByID(Properties::RMHAO)
-								};
+									// Get material properties
+									auto materialsProperty = meshesProperty.getPropertySet(iMesh).getPropertySetByID(Properties::Materials);
 
-								// Go over each material type
-								for(unsigned int iMatType = 0; iMatType < MaterialType::MaterialType_NumOfTypes; iMatType++)
-								{
-									// Check if an entry for the current material type was present within the properties
-									if(materialProperties[iMatType])
+									// Define material data and material properties
+									MaterialData materials[MaterialType::MaterialType_NumOfTypes];
+									PropertySet materialProperties[MaterialType::MaterialType_NumOfTypes] =
 									{
-										// Get texture filename property, check if it is valid
-										auto filenameProperty = materialProperties[iMatType].getPropertyByID(Properties::Filename);
-										if(filenameProperty.isVariableTypeString())
+										materialsProperty.getPropertySetByID(Properties::Diffuse),
+										materialsProperty.getPropertySetByID(Properties::Normal),
+										materialsProperty.getPropertySetByID(Properties::Emissive),
+										materialsProperty.getPropertySetByID(Properties::RMHAO)
+									};
+
+									// Go over each material type
+									for(unsigned int iMatType = 0; iMatType < MaterialType::MaterialType_NumOfTypes; iMatType++)
+									{
+										// Check if an entry for the current material type was present within the properties
+										if(materialProperties[iMatType])
 										{
-											// Get texture filename string, check if it is valid
-											m_materialsFromProperties->m_meshMaterials[meshDataIndex][iMatType] = filenameProperty.getString();
+											// Get texture filename property, check if it is valid
+											auto filenameProperty = materialProperties[iMatType].getPropertyByID(Properties::Filename);
+											if(filenameProperty.isVariableTypeString())
+											{
+												// Get texture filename string, check if it is valid
+												m_materialsFromProperties->m_meshMaterials[meshDataIndex][iMatType] = filenameProperty.getString();
+											}
+
+											// Get texture scale property, check if it is valid
+											auto scaleProperty = materialProperties[iMatType].getPropertyByID(Properties::TextureScale);
+											if(scaleProperty)
+												m_materialsFromProperties->m_meshMaterialsScale[meshDataIndex][iMatType] = scaleProperty.getVec2f();
 										}
-
-										// Get texture scale property, check if it is valid
-										auto scaleProperty = materialProperties[iMatType].getPropertyByID(Properties::TextureScale);
-										if(scaleProperty)
-											m_materialsFromProperties->m_meshMaterialsScale[meshDataIndex][iMatType] = scaleProperty.getVec2f();
 									}
 								}
 							}
 						}
-					}
 
-					/*/ Loop over each mesh entry in the model node
-					for(decltype(meshesProperty.getNumPropertySets()) iMesh = 0, numMeshes = meshesProperty.getNumPropertySets(); iMesh < numMeshes; iMesh++)
-					{
-						// Try to get the mesh index property node and check if it is present
-						auto &meshIndexProperty = meshesProperty.getPropertySet(iMesh).getPropertyByID(Properties::Index);
-						if(meshIndexProperty)
+						/*/ Loop over each mesh entry in the model node
+						for(decltype(meshesProperty.getNumPropertySets()) iMesh = 0, numMeshes = meshesProperty.getNumPropertySets(); iMesh < numMeshes; iMesh++)
 						{
-							// Get the mesh index, check if it is valid and within the range of mesh array that was loaded from the model
-							const int meshDataIndex = meshIndexProperty.getInt();
-							if(meshDataIndex >= 0 && meshDataIndex < meshesInModelArray.size())
+							// Try to get the mesh index property node and check if it is present
+							auto &meshIndexProperty = meshesProperty.getPropertySet(iMesh).getPropertyByID(Properties::Index);
+							if(meshIndexProperty)
 							{
-								// Get material properties
-								auto materialsProperty = meshesProperty.getPropertySet(iMesh).getPropertySetByID(Properties::Materials);
-
-								// Define material data and material properties
-								MaterialData materials[MaterialType::MaterialType_NumOfTypes];
-								PropertySet materialProperties[MaterialType::MaterialType_NumOfTypes] =
+								// Get the mesh index, check if it is valid and within the range of mesh array that was loaded from the model
+								const int meshDataIndex = meshIndexProperty.getInt();
+								if(meshDataIndex >= 0 && meshDataIndex < meshesInModelArray.size())
 								{
-									materialsProperty.getPropertySetByID(Properties::Diffuse),
-									materialsProperty.getPropertySetByID(Properties::Normal),
-									materialsProperty.getPropertySetByID(Properties::Emissive),
-									materialsProperty.getPropertySetByID(Properties::RMHAO)
-								};
+									// Get material properties
+									auto materialsProperty = meshesProperty.getPropertySet(iMesh).getPropertySetByID(Properties::Materials);
 
-								// Go over each material type
-								for(unsigned int iMatType = 0; iMatType < MaterialType::MaterialType_NumOfTypes; iMatType++)
-								{
-									// Check if an entry for the current material type was present within the properties
-									if(materialProperties[iMatType])
+									// Define material data and material properties
+									MaterialData materials[MaterialType::MaterialType_NumOfTypes];
+									PropertySet materialProperties[MaterialType::MaterialType_NumOfTypes] =
 									{
-										// Load the material data
-										materials[iMatType] = loadMaterialData(materialProperties[iMatType], newModelData.m_model.getMaterialArrays(), static_cast<MaterialType>(iMatType), meshDataIndex);
+										materialsProperty.getPropertySetByID(Properties::Diffuse),
+										materialsProperty.getPropertySetByID(Properties::Normal),
+										materialsProperty.getPropertySetByID(Properties::Emissive),
+										materialsProperty.getPropertySetByID(Properties::RMHAO)
+									};
+
+									// Go over each material type
+									for(unsigned int iMatType = 0; iMatType < MaterialType::MaterialType_NumOfTypes; iMatType++)
+									{
+										// Check if an entry for the current material type was present within the properties
+										if(materialProperties[iMatType])
+										{
+											// Load the material data
+											materials[iMatType] = loadMaterialData(materialProperties[iMatType], newModelData.m_model.getMaterialArrays(), static_cast<MaterialType>(iMatType), meshDataIndex);
+										}
 									}
-								}
 
-								newModelData.m_meshes.push_back(MeshData(meshesInModelArray[iMesh], materials));
+									newModelData.m_meshes.push_back(MeshData(meshesInModelArray[iMesh], materials));
 
-								ErrHandlerLoc().get().log(ErrorType::Info, ErrorSource::Source_ModelComponent, m_name + " - Model \'" + modelName + "\' imported");
+									ErrHandlerLoc().get().log(ErrorType::Info, ErrorSource::Source_ModelComponent, m_name + " - Model \'" + modelName + "\' imported");
+								}
 							}
-						}
-					}*/
-				}
-				/*else
-				{
-					// Get the material arrays that were loaded from the model file
-					auto &materialArrayFromModel = newModelData.m_model.getMaterialArrays();
-
-					// Iterate over every mesh in the model
-					for(decltype(meshesInModelArray.size()) iMesh = 0, numMeshes = meshesInModelArray.size(); iMesh < numMeshes; iMesh++)
+						}*/
+					}
+					/*else
 					{
-						// Define material data and material properties
-						MaterialData materials[MaterialType::MaterialType_NumOfTypes];
+						// Get the material arrays that were loaded from the model file
+						auto &materialArrayFromModel = newModelData.m_model.getMaterialArrays();
 
-						// Go over each mesh in the model
-						//if(iMesh > materialArrayFromModel.m_numMaterials)
+						// Iterate over every mesh in the model
+						for(decltype(meshesInModelArray.size()) iMesh = 0, numMeshes = meshesInModelArray.size(); iMesh < numMeshes; iMesh++)
 						{
-							// Go over each material type
-							for(unsigned int iMatType = 0; iMatType < MaterialType::MaterialType_NumOfTypes; iMatType++)
-							{
-								// Get the texture filename and load it to memory
-								auto textureFromModel = Loaders::texture2D().load(materialArrayFromModel.m_materials[iMatType][iMesh].m_filename, static_cast<MaterialType>(iMatType), false);
-								auto materialLoadError = textureFromModel.loadToMemory();
+							// Define material data and material properties
+							MaterialData materials[MaterialType::MaterialType_NumOfTypes];
 
-								// Check if the texture was loaded successfully
-								if(materialLoadError == ErrorCode::Success)
-								{
-									materials[MaterialType::MaterialType_Diffuse].m_texture = textureFromModel;
-								}
-								else
+							// Go over each mesh in the model
+							//if(iMesh > materialArrayFromModel.m_numMaterials)
+							{
+								// Go over each material type
+								for(unsigned int iMatType = 0; iMatType < MaterialType::MaterialType_NumOfTypes; iMatType++)
 								{
-									ErrHandlerLoc::get().log(materialLoadError, ErrorSource::Source_Renderer);
+									// Get the texture filename and load it to memory
+									auto textureFromModel = Loaders::texture2D().load(materialArrayFromModel.m_materials[iMatType][iMesh].m_filename, static_cast<MaterialType>(iMatType), false);
+									auto materialLoadError = textureFromModel.loadToMemory();
+
+									// Check if the texture was loaded successfully
+									if(materialLoadError == ErrorCode::Success)
+									{
+										materials[MaterialType::MaterialType_Diffuse].m_texture = textureFromModel;
+									}
+									else
+									{
+										ErrHandlerLoc::get().log(materialLoadError, ErrorSource::Source_Renderer);
+									}
 								}
-							}
 
-							// Add the data for this mesh. Include materials loaded from the model itself, if they were present, otherwise, include default textures instead
-							newModelData.m_meshes.push_back(MeshData(meshesInModelArray[iMesh], materials));
+								// Add the data for this mesh. Include materials loaded from the model itself, if they were present, otherwise, include default textures instead
+								newModelData.m_meshes.push_back(MeshData(meshesInModelArray[iMesh], materials));
 
-							ErrHandlerLoc().get().log(ErrorType::Info, ErrorSource::Source_ModelComponent, m_name + " - Model \'" + modelName + "\' imported");
+								ErrHandlerLoc().get().log(ErrorType::Info, ErrorSource::Source_ModelComponent, m_name + " - Model \'" + modelName + "\' imported");
+							}
 						}
-					}
-				}*/
+					}*/
+				}
 			}
 
 			if(p_properties.getNumPropertySets() == 0)
@@ -363,6 +368,8 @@ public:
 		setLoadedToVideoMemory(true);
 	}
 
+	const inline std::vector<ModelData> &getModelData() const { return m_modelData; }
+
 private:
 	struct MeshMaterialsProperties
 	{

+ 1 - 2
Praxis3D/Source/ModelLoader.cpp

@@ -116,8 +116,7 @@ void Model::loadFromFile()
 	// If data restructuring was successful, set loaded to memory flag
 	if(error == ErrorCode::Success)
 		setLoadedToMemory(true);
-	// If data restructuring failed, log an error
-	else
+	else // If data restructuring failed, log an error
 		ErrHandlerLoc::get().log(error, ErrorSource::Source_ModelLoader);
 }
 ErrorCode Model::loadFromScene(const aiScene &p_assimpScene)

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff