Bladeren bron

resource: fully review the resource module

Michele Caini 3 jaren geleden
bovenliggende
commit
c54409bdfb
8 gewijzigde bestanden met toevoegingen van 549 en 394 verwijderingen
  1. 2 1
      CMakeLists.txt
  2. 2 2
      TODO
  3. 2 1
      src/entt/entt.hpp
  4. 316 172
      src/entt/resource/cache.hpp
  5. 7 5
      src/entt/resource/fwd.hpp
  6. 0 213
      src/entt/resource/handle.hpp
  7. 33 0
      src/entt/resource/loader.hpp
  8. 187 0
      src/entt/resource/resource.hpp

+ 2 - 1
CMakeLists.txt

@@ -170,7 +170,8 @@ if(ENTT_INCLUDE_HEADERS)
             $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/process/scheduler.hpp>
             $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/resource/cache.hpp>
             $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/resource/fwd.hpp>
-            $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/resource/handle.hpp>
+            $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/resource/loader.hpp>
+            $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/resource/resource.hpp>
             $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/signal/delegate.hpp>
             $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/signal/dispatcher.hpp>
             $<BUILD_INTERFACE:${EnTT_SOURCE_DIR}/src/entt/signal/emitter.hpp>

+ 2 - 2
TODO

@@ -3,8 +3,8 @@
 * add examples (and credits) from @alanjfs :)
 
 WIP:
-* resource: tag dispatching loader example, allocator support
-* uses-allocator construction: any (with allocator support), cache, poly, ...
+* type_id(T &&)
+* uses-allocator construction: any (with allocator support), poly, ...
 * get rid of storage_traits class template
 * process scheduler: reviews, use free lists internally
 * runtime events (emitter)

+ 2 - 1
src/entt/entt.hpp

@@ -52,7 +52,8 @@
 #include "process/process.hpp"
 #include "process/scheduler.hpp"
 #include "resource/cache.hpp"
-#include "resource/handle.hpp"
+#include "resource/loader.hpp"
+#include "resource/resource.hpp"
 #include "signal/delegate.hpp"
 #include "signal/dispatcher.hpp"
 #include "signal/emitter.hpp"

+ 316 - 172
src/entt/resource/cache.hpp

@@ -1,56 +1,288 @@
-#ifndef ENTT_RESOURCE_CACHE_HPP
-#define ENTT_RESOURCE_CACHE_HPP
+#ifndef ENTT_RESOURCE_RESOURCE_CACHE_HPP
+#define ENTT_RESOURCE_RESOURCE_CACHE_HPP
 
+#include <cstddef>
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <tuple>
 #include <type_traits>
 #include <utility>
 #include "../config/config.h"
 #include "../container/dense_map.hpp"
 #include "../core/compressed_pair.hpp"
 #include "../core/fwd.hpp"
+#include "../core/iterator.hpp"
 #include "../core/utility.hpp"
 #include "fwd.hpp"
-#include "handle.hpp"
+#include "loader.hpp"
+#include "resource.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.
+ * @cond TURN_OFF_DOXYGEN
+ * Internal details not to be documented.
+ */
+
+namespace internal {
+
+template<typename Type, typename It>
+class resource_cache_iterator final {
+    template<typename, typename>
+    friend class resource_cache_iterator;
+
+public:
+    using value_type = std::pair<id_type, resource<Type>>;
+    using pointer = input_iterator_pointer<value_type>;
+    using reference = value_type;
+    using difference_type = std::ptrdiff_t;
+    using iterator_category = std::input_iterator_tag;
+
+    resource_cache_iterator() ENTT_NOEXCEPT = default;
+
+    resource_cache_iterator(const It iter) ENTT_NOEXCEPT
+        : it{iter} {}
+
+    template<typename Other, typename = std::enable_if_t<std::is_const_v<Type>>>
+    resource_cache_iterator(const resource_cache_iterator<std::remove_const_t<Type>, Other> &other) ENTT_NOEXCEPT
+        : it{other.it} {}
+
+    resource_cache_iterator &operator++() ENTT_NOEXCEPT {
+        return ++it, *this;
+    }
+
+    resource_cache_iterator operator++(int) ENTT_NOEXCEPT {
+        resource_cache_iterator orig = *this;
+        return ++(*this), orig;
+    }
+
+    resource_cache_iterator &operator--() ENTT_NOEXCEPT {
+        return --it, *this;
+    }
+
+    resource_cache_iterator operator--(int) ENTT_NOEXCEPT {
+        resource_cache_iterator orig = *this;
+        return operator--(), orig;
+    }
+
+    resource_cache_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT {
+        it += value;
+        return *this;
+    }
+
+    resource_cache_iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
+        resource_cache_iterator copy = *this;
+        return (copy += value);
+    }
+
+    resource_cache_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT {
+        return (*this += -value);
+    }
+
+    resource_cache_iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
+        return (*this + -value);
+    }
+
+    [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT {
+        return {it[value].first, resource<Type>{it[value].second}};
+    }
+
+    [[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
+        return (*this)[0];
+    }
+
+    [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
+        return operator*();
+    }
+
+    template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
+    friend std::ptrdiff_t operator-(const resource_cache_iterator<TLhs, ILhs> &, const resource_cache_iterator<TRhs, IRhs> &) ENTT_NOEXCEPT;
+
+    template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
+    friend bool operator==(const resource_cache_iterator<TLhs, ILhs> &, const resource_cache_iterator<TRhs, IRhs> &) ENTT_NOEXCEPT;
+
+    template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
+    friend bool operator<(const resource_cache_iterator<TLhs, ILhs> &, const resource_cache_iterator<TRhs, IRhs> &) ENTT_NOEXCEPT;
+
+private:
+    It it;
+};
+
+template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
+[[nodiscard]] std::ptrdiff_t operator-(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) ENTT_NOEXCEPT {
+    return lhs.it - rhs.it;
+}
+
+template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
+[[nodiscard]] bool operator==(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) ENTT_NOEXCEPT {
+    return lhs.it == rhs.it;
+}
+
+template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
+[[nodiscard]] bool operator!=(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) ENTT_NOEXCEPT {
+    return !(lhs == rhs);
+}
+
+template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
+[[nodiscard]] bool operator<(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) ENTT_NOEXCEPT {
+    return lhs.it < rhs.it;
+}
+
+template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
+[[nodiscard]] bool operator>(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) ENTT_NOEXCEPT {
+    return rhs < lhs;
+}
+
+template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
+[[nodiscard]] bool operator<=(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) ENTT_NOEXCEPT {
+    return !(lhs > rhs);
+}
+
+template<typename TLhs, typename ILhs, typename TRhs, typename IRhs>
+[[nodiscard]] bool operator>=(const resource_cache_iterator<TLhs, ILhs> &lhs, const resource_cache_iterator<TRhs, IRhs> &rhs) ENTT_NOEXCEPT {
+    return !(lhs < rhs);
+}
+
+} // namespace internal
+
+/**
+ * Internal details not to be documented.
+ * @endcond
+ */
+
+/**
+ * @brief Basic cache for resources of any type.
+ * @tparam Type Type of resources managed by a cache.
  * @tparam Loader Type of loader used to create the resources.
+ * @tparam Allocator Type of allocator used to manage memory and elements.
  */
-template<typename Resource, typename Loader>
+template<typename Type, typename Loader, typename Allocator>
 class resource_cache {
-    static_assert(std::is_base_of_v<Resource, typename Loader::value_type>, "Invalid loader");
+    using alloc_traits = typename std::allocator_traits<Allocator>;
+    static_assert(std::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type");
+    using container_allocator = typename alloc_traits::template rebind_alloc<std::pair<const id_type, typename Loader::result_type>>;
+    using container_type = dense_map<id_type, typename Loader::result_type, identity, std::equal_to<id_type>, container_allocator>;
 
 public:
+    /*! @brief Resource type. */
+    using value_type = Type;
     /*! @brief Unsigned integer type. */
     using size_type = std::size_t;
-    /*! @brief Resource type. */
-    using resource_type = Resource;
     /*! @brief Loader type. */
     using loader_type = Loader;
+    /*! @brief Allocator type. */
+    using allocator_type = Allocator;
+    /*! @brief Input iterator type. */
+    using iterator = internal::resource_cache_iterator<Type, typename container_type::iterator>;
+    /*! @brief Constant input iterator type. */
+    using const_iterator = internal::resource_cache_iterator<const Type, typename container_type::const_iterator>;
 
     /*! @brief Default constructor. */
-    resource_cache() = default;
+    resource_cache()
+        : resource_cache{loader_type{}} {}
+
+    /**
+     * @brief Constructs an empty cache with a given allocator.
+     * @param allocator The allocator to use.
+     */
+    explicit resource_cache(const allocator_type &allocator)
+        : resource_cache{loader_type{}, allocator} {}
+
+    /**
+     * @brief Constructs an empty cache with a given allocator and loader.
+     * @param callable The loader to use.
+     * @param allocator The allocator to use.
+     */
+    explicit resource_cache(const loader_type &callable, const allocator_type &allocator = allocator_type{})
+        : pool{container_type{allocator}, callable} {}
+
+    /*! @brief Default copy constructor. */
+    resource_cache(const resource_cache &) = default;
+
+    /**
+     * @brief Allocator-extended copy constructor.
+     * @param other The instance to copy from.
+     * @param allocator The allocator to use.
+     */
+    resource_cache(const resource_cache &other, const allocator_type &allocator)
+        : pool{std::piecewise_construct, std::forward_as_tuple(other.pool.first(), allocator), std::forward_as_tuple(other.pool.second())} {}
 
     /*! @brief Default move constructor. */
     resource_cache(resource_cache &&) = default;
 
-    /*! @brief Default move assignment operator. @return This cache. */
+    /**
+     * @brief Allocator-extended move constructor.
+     * @param other The instance to move from.
+     * @param allocator The allocator to use.
+     */
+    resource_cache(resource_cache &&other, const allocator_type &allocator)
+        : pool{std::piecewise_construct, std::forward_as_tuple(std::move(other.pool.first()), allocator), std::forward_as_tuple(std::move(other.pool.second()))} {}
+
+    /**
+     * @brief Default copy assignment operator.
+     * @return This cache.
+     */
+    resource_cache &operator=(const resource_cache &) = default;
+
+    /**
+     * @brief Default move assignment operator.
+     * @return This cache.
+     */
     resource_cache &operator=(resource_cache &&) = default;
 
     /**
-     * @brief Number of resources managed by a cache.
-     * @return Number of resources currently stored.
+     * @brief Returns the associated allocator.
+     * @return The associated allocator.
      */
-    [[nodiscard]] size_type size() const ENTT_NOEXCEPT {
-        return resources.size();
+    [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT {
+        return pool.first().get_allocator();
+    }
+
+    /**
+     * @brief Returns an iterator to the beginning.
+     *
+     * The returned iterator points to the first instance of the cache. If the
+     * cache is empty, the returned iterator will be equal to `end()`.
+     *
+     * @return An iterator to the first instance of the internal cache.
+     */
+    [[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT {
+        return pool.first().begin();
+    }
+
+    /*! @copydoc cbegin */
+    [[nodiscard]] const_iterator begin() const ENTT_NOEXCEPT {
+        return cbegin();
+    }
+
+    /*! @copydoc begin */
+    [[nodiscard]] iterator begin() ENTT_NOEXCEPT {
+        return pool.first().begin();
+    }
+
+    /**
+     * @brief Returns an iterator to the end.
+     *
+     * The returned iterator points to the element following the last instance
+     * of the cache. Attempting to dereference the returned iterator results in
+     * undefined behavior.
+     *
+     * @return An iterator to the element following the last instance of the
+     * internal cache.
+     */
+    [[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT {
+        return pool.first().end();
+    }
+
+    /*! @copydoc cend */
+    [[nodiscard]] const_iterator end() const ENTT_NOEXCEPT {
+        return cend();
+    }
+
+    /*! @copydoc end */
+    [[nodiscard]] iterator end() ENTT_NOEXCEPT {
+        return pool.first().end();
     }
 
     /**
@@ -58,121 +290,79 @@ public:
      * @return True if the cache contains no resources, false otherwise.
      */
     [[nodiscard]] bool empty() const ENTT_NOEXCEPT {
-        return resources.empty();
+        return pool.first().empty();
     }
 
     /**
-     * @brief Clears a cache and discards all its resources.
-     *
-     * Handles are not invalidated and the memory used by a resource isn't
-     * freed as long as at least a handle keeps the resource itself alive.
+     * @brief Number of resources managed by a cache.
+     * @return Number of resources currently stored.
      */
+    [[nodiscard]] size_type size() const ENTT_NOEXCEPT {
+        return pool.first().size();
+    }
+
+    /*! @brief Clears a cache. */
     void clear() ENTT_NOEXCEPT {
-        resources.clear();
+        pool.first().clear();
     }
 
     /**
-     * @brief Loads the resource that corresponds to a given identifier.
-     *
-     * 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.
+     * @brief Loads a resource, if its identifier does not exist.
      *
-     * @note
-     * If the identifier is already present in the cache, this function does
-     * nothing and the arguments are simply discarded.
+     * Arguments are forwarded directly to the loader and _consumed_ only if the
+     * resource doesn't already exist.
      *
      * @warning
-     * If the resource cannot be loaded correctly, the returned handle will be
+     * If the resource isn't loaded correctly, the returned handle could be
      * invalid and any use of it will result in undefined behavior.
      *
      * @tparam Args Types of arguments to use to load the resource if required.
      * @param id Unique resource identifier.
      * @param args Arguments to use to load the resource if required.
-     * @return A handle for the given resource.
+     * @return A pair consisting of an iterator to the inserted element (or to
+     * the element that prevented the insertion) and a bool denoting whether the
+     * insertion took place.
      */
     template<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(std::forward<Args>(args)...); handle) {
-                return (resources[id] = std::move(handle));
-            }
-        } else {
-            return it->second;
+    std::pair<iterator, bool> load(const id_type id, Args &&...args) {
+        if(auto it = pool.first().find(id); it != pool.first().end()) {
+            return {it, false};
         }
 
-        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 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... Args>
-    resource_handle<resource_type> reload(const id_type id, Args &&...args) {
-        return (discard(id), load(id, std::forward<Args>(args)...));
+        return pool.first().emplace(id, pool.second()(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 Args Types of arguments to use to load the resource.
-     * @param args Arguments to use to load the resource.
-     * @return A handle for the given resource.
+     * @brief Force loads a resource, if its identifier does not exist.
+     * @copydetails load
      */
     template<typename... Args>
-    [[nodiscard]] resource_handle<resource_type> temp(Args &&...args) const {
-        return loader_type{}(std::forward<Args>(args)...);
+    std::pair<iterator, bool> force_load(const id_type id, Args &&...args) {
+        return {pool.first().insert_or_assign(id, pool.second()(std::forward<Args>(args)...)).first, true};
     }
 
     /**
-     * @brief Creates a handle for a given resource identifier.
+     * @brief Returns 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
+     * @warning
+     * There is no guarantee that the returned handle is valid.<br/>
+     * If it is not, any use will result in indefinite behavior.
      *
      * @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;
+    [[nodiscard]] resource<const value_type> operator[](const id_type id) const {
+        if(auto it = pool.first().find(id); it != pool.first().cend()) {
+            return resource<const value_type>{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;
+    /*! @copydoc operator[] */
+    [[nodiscard]] resource<value_type> operator[](const id_type id) {
+        if(auto it = pool.first().find(id); it != pool.first().end()) {
+            return resource<value_type>{it->second};
         }
 
         return {};
@@ -184,95 +374,49 @@ public:
      * @return True if the cache contains the resource, false otherwise.
      */
     [[nodiscard]] bool contains(const id_type id) const {
-        return (resources.find(id) != resources.cend());
+        return pool.first().contains(id);
     }
 
     /**
-     * @brief Discards the resource that corresponds to a given identifier.
-     *
-     * Handles are not invalidated and the memory used by the resource isn't
-     * freed as long as at least a handle keeps the resource itself alive.
-     *
-     * @param id Unique resource identifier.
+     * @brief Removes an element from a given position.
+     * @param pos An iterator to the element to remove.
+     * @return An iterator following the removed element.
      */
-    void discard(const id_type id) {
-        if(auto it = resources.find(id); it != resources.end()) {
-            resources.erase(it);
-        }
+    iterator erase(const_iterator pos) {
+        const auto it = pool.first().begin();
+        return pool.first().erase(it + (pos - const_iterator{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.
+     * @brief Removes the given elements from a cache.
+     * @param first An iterator to the first element of the range of elements.
+     * @param last An iterator past the last element of the range of elements.
+     * @return An iterator following the last removed element.
      */
-    template<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});
-            }
-        }
+    iterator erase(const_iterator first, const_iterator last) {
+        const auto it = pool.first().begin();
+        return pool.first().erase(it + (first - const_iterator{it}), it + (last - const_iterator{it}));
     }
 
     /**
-     * @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.
+     * @brief Removes the given elements from a cache.
+     * @param id Unique resource identifier.
+     * @return Number of resources erased (either 0 or 1).
      */
-    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);
-            }
-        }
+    size_type erase(const id_type id) {
+        return pool.first().erase(id);
+    }
+
+    /**
+     * @brief Returns the loader used to create resources.
+     * @return The loader used to create resources.
+     */
+    [[nodiscard]] loader_type loader() const {
+        return pool.second();
     }
 
 private:
-    dense_map<id_type, resource_handle<resource_type>, identity> resources;
+    compressed_pair<container_type, loader_type> pool;
 };
 
 } // namespace entt

+ 7 - 5
src/entt/resource/fwd.hpp

@@ -1,16 +1,18 @@
 #ifndef ENTT_RESOURCE_FWD_HPP
 #define ENTT_RESOURCE_FWD_HPP
 
+#include <memory>
+
 namespace entt {
 
-template<typename, typename>
+template<typename>
+struct resource_loader;
+
+template<typename Type, typename = resource_loader<Type>, typename = std::allocator<Type>>
 class resource_cache;
 
 template<typename>
-class resource_handle;
-
-template<typename, typename>
-class resource_loader;
+class resource;
 
 } // namespace entt
 

+ 0 - 213
src/entt/resource/handle.hpp

@@ -1,213 +0,0 @@
-#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 Value type. */
-    using value_type = Resource;
-    /*! @brief Reference type. */
-    using reference = value_type &;
-    /*! @brief Pointer type. */
-    using pointer = value_type *;
-    /*! @brief Unsigned integer type. */
-    using size_type = long;
-
-    /*! @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<value_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, value_type &res) ENTT_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<value_type, Other> && std::is_base_of_v<value_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<value_type, Other> && std::is_base_of_v<value_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<value_type, Other> && std::is_base_of_v<value_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<value_type, Other> && std::is_base_of_v<value_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]] reference get() const ENTT_NOEXCEPT {
-        return *resource;
-    }
-
-    /*! @copydoc get */
-    [[nodiscard]] operator reference() const ENTT_NOEXCEPT {
-        return get();
-    }
-
-    /*! @copydoc get */
-    [[nodiscard]] reference 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]] pointer 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<value_type> resource;
-};
-
-/**
- * @brief Compares two handles.
- * @tparam Res Type of resource managed by the first handle.
- * @tparam Other Type of resource managed by the second handle.
- * @param lhs A valid handle.
- * @param rhs A valid handle.
- * @return True if both handles refer to the same resource, false otherwise.
- */
-template<typename Res, typename Other>
-[[nodiscard]] bool operator==(const resource_handle<Res> &lhs, const resource_handle<Other> &rhs) ENTT_NOEXCEPT {
-    return lhs.operator->() == rhs.operator->();
-}
-
-/**
- * @brief Compares two handles.
- * @tparam Res Type of resource managed by the first handle.
- * @tparam Other Type of resource managed by the second handle.
- * @param lhs A valid handle.
- * @param rhs A valid handle.
- * @return False if both handles refer to the same registry, true otherwise.
- */
-template<typename ILhs, typename IRhs>
-[[nodiscard]] bool operator!=(const resource_handle<ILhs> &lhs, const resource_handle<IRhs> &rhs) ENTT_NOEXCEPT {
-    return !(lhs == rhs);
-}
-
-} // namespace entt
-
-#endif

+ 33 - 0
src/entt/resource/loader.hpp

@@ -0,0 +1,33 @@
+#ifndef ENTT_RESOURCE_LOADEr_HPP
+#define ENTT_RESOURCE_LOADEr_HPP
+
+#include <memory>
+#include <utility>
+#include "fwd.hpp"
+
+namespace entt {
+
+/**
+ * @brief Transparent loader for shared resources.
+ * @tparam Type Type of resources created by the loader.
+ */
+template<typename Type>
+struct resource_loader {
+    /*! @brief Result type. */
+    using result_type = std::shared_ptr<Type>;
+
+    /**
+     * @brief Constructs a shared pointer to a resource from its arguments.
+     * @tparam Args Types of arguments to use to construct the resource.
+     * @param args Parameters to use to construct the resource.
+     * @return A shared pointer to a resource of the given type.
+     */
+    template<typename... Args>
+    result_type operator()(Args &&...args) const {
+        return std::make_shared<Type>(std::forward<Args>(args)...);
+    }
+};
+
+} // namespace entt
+
+#endif

+ 187 - 0
src/entt/resource/resource.hpp

@@ -0,0 +1,187 @@
+#ifndef ENTT_RESOURCE_RESOURCE_HPP
+#define ENTT_RESOURCE_RESOURCE_HPP
+
+#include <memory>
+#include <type_traits>
+#include <utility>
+#include "../config/config.h"
+#include "fwd.hpp"
+
+namespace entt {
+
+/**
+ * @brief Basic resource handle.
+ *
+ * A handle wraps a resource and extends its lifetime. It also shares the same
+ * resource with all other handles constructed from the same element.<br/>
+ * As a rule of thumb, resources should never be copied nor moved. Handles are
+ * the way to go to push references around.
+ *
+ * @tparam Type Type of resource managed by a handle.
+ */
+template<typename Type>
+class resource {
+    /*! @brief Resource handles are friends with each other. */
+    template<typename>
+    friend class resource;
+
+    template<typename Other>
+    static constexpr bool is_acceptable_v = !std::is_same_v<Type, Other> && std::is_constructible_v<Type &, Other &>;
+
+public:
+    /*! @brief Default constructor. */
+    resource() ENTT_NOEXCEPT = default;
+
+    /**
+     * @brief Creates a handle from a weak pointer, namely a resource.
+     * @param res A weak pointer to a resource.
+     */
+    explicit resource(std::shared_ptr<Type> res) ENTT_NOEXCEPT
+        : value{std::move(res)} {}
+
+    /*! @brief Default copy constructor. */
+    resource(const resource &) ENTT_NOEXCEPT = default;
+
+    /*! @brief Default move constructor. */
+    resource(resource &&) ENTT_NOEXCEPT = default;
+
+    /**
+     * @brief Aliasing constructor.
+     * @tparam Other Type of resource managed by the received handle.
+     * @param other The handle with which to share ownership information.
+     * @param res Unrelated and unmanaged resources.
+     */
+    template<typename Other>
+    resource(const resource<Other> &other, Type &res) ENTT_NOEXCEPT
+        : value{other.value, std::addressof(res)} {}
+
+    /**
+     * @brief Copy constructs a handle which shares ownership of the resource.
+     * @tparam Other Type of resource managed by the received handle.
+     * @param other The handle to copy from.
+     */
+    template<typename Other, typename = std::enable_if_t<is_acceptable_v<Other>>>
+    resource(const resource<Other> &other) ENTT_NOEXCEPT
+        : value{other.value} {}
+
+    /**
+     * @brief Move constructs a handle which takes ownership of the resource.
+     * @tparam Other Type of resource managed by the received handle.
+     * @param other The handle to move from.
+     */
+    template<typename Other, typename = std::enable_if_t<is_acceptable_v<Other>>>
+    resource(resource<Other> &&other) ENTT_NOEXCEPT
+        : value{std::move(other.value)} {}
+
+    /**
+     * @brief Default copy assignment operator.
+     * @return This resource handle.
+     */
+    resource &operator=(const resource &) ENTT_NOEXCEPT = default;
+
+    /**
+     * @brief Default move assignment operator.
+     * @return This resource handle.
+     */
+    resource &operator=(resource &&) ENTT_NOEXCEPT = default;
+
+    /**
+     * @brief Copy assignment operator from foreign handle.
+     * @tparam Other Type of resource managed by the received handle.
+     * @param other The handle to copy from.
+     * @return This resource handle.
+     */
+    template<typename Other>
+    std::enable_if_t<is_acceptable_v<Other>, resource &>
+    operator=(const resource<Other> &other) ENTT_NOEXCEPT {
+        value = other.value;
+        return *this;
+    }
+
+    /**
+     * @brief Move assignment operator from foreign handle.
+     * @tparam Other Type of resource managed by the received handle.
+     * @param other The handle to move from.
+     * @return This resource handle.
+     */
+    template<typename Other>
+    std::enable_if_t<is_acceptable_v<Other>, resource &>
+    operator=(resource<Other> &&other) ENTT_NOEXCEPT {
+        value = std::move(other.value);
+        return *this;
+    }
+
+    /**
+     * @brief Returns a reference to the managed resource.
+     *
+     * @warning
+     * The behavior is undefined if the handle doesn't contain a resource.
+     *
+     * @return A reference to the managed resource.
+     */
+    [[nodiscard]] Type &operator*() const ENTT_NOEXCEPT {
+        return *value;
+    }
+
+    /*! @copydoc operator* */
+    [[nodiscard]] operator Type &() const ENTT_NOEXCEPT {
+        return *value;
+    }
+
+    /**
+     * @brief Returns a pointer to the managed resource.
+     * @return A pointer to the managed resource.
+     */
+    [[nodiscard]] Type *operator->() const ENTT_NOEXCEPT {
+        return value.get();
+    }
+
+    /**
+     * @brief Returns true if a handle contains a resource, false otherwise.
+     * @return True if the handle contains a resource, false otherwise.
+     */
+    [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
+        return static_cast<bool>(value);
+    }
+
+    /**
+     * @brief Returns the number of handles pointing the same resource.
+     * @return The number of handles pointing the same resource.
+     */
+    [[nodiscard]] long use_count() const ENTT_NOEXCEPT {
+        return value.use_count();
+    }
+
+private:
+    std::shared_ptr<Type> value;
+};
+
+/**
+ * @brief Compares two handles.
+ * @tparam Res Type of resource managed by the first handle.
+ * @tparam Other Type of resource managed by the second handle.
+ * @param lhs A valid handle.
+ * @param rhs A valid handle.
+ * @return True if both handles refer to the same resource, false otherwise.
+ */
+template<typename Res, typename Other>
+[[nodiscard]] bool operator==(const resource<Res> &lhs, const resource<Other> &rhs) ENTT_NOEXCEPT {
+    return (std::addressof(*lhs) == std::addressof(*rhs));
+}
+
+/**
+ * @brief Compares two handles.
+ * @tparam Res Type of resource managed by the first handle.
+ * @tparam Other Type of resource managed by the second handle.
+ * @param lhs A valid handle.
+ * @param rhs A valid handle.
+ * @return False if both handles refer to the same registry, true otherwise.
+ */
+template<typename ILhs, typename IRhs>
+[[nodiscard]] bool operator!=(const resource<ILhs> &lhs, const resource<IRhs> &rhs) ENTT_NOEXCEPT {
+    return !(lhs == rhs);
+}
+
+} // namespace entt
+
+#endif