From c54409bdfbfdc4a17af77d31c6a960199ea91b25 Mon Sep 17 00:00:00 2001 From: Michele Caini Date: Thu, 31 Mar 2022 15:43:07 +0200 Subject: [PATCH] resource: fully review the resource module --- CMakeLists.txt | 3 +- TODO | 4 +- src/entt/entt.hpp | 3 +- src/entt/resource/cache.hpp | 506 +++++++++++++++++++++------------ src/entt/resource/fwd.hpp | 12 +- src/entt/resource/handle.hpp | 213 -------------- src/entt/resource/loader.hpp | 33 +++ src/entt/resource/resource.hpp | 187 ++++++++++++ 8 files changed, 558 insertions(+), 403 deletions(-) delete mode 100644 src/entt/resource/handle.hpp create mode 100644 src/entt/resource/loader.hpp create mode 100644 src/entt/resource/resource.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f60cbe4f9..4518cab7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -170,7 +170,8 @@ if(ENTT_INCLUDE_HEADERS) $ $ $ - $ + $ + $ $ $ $ diff --git a/TODO b/TODO index aa4eef0e2..349398051 100644 --- a/TODO +++ b/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) diff --git a/src/entt/entt.hpp b/src/entt/entt.hpp index f0fdef1f2..8ab5bf940 100644 --- a/src/entt/entt.hpp +++ b/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" diff --git a/src/entt/resource/cache.hpp b/src/entt/resource/cache.hpp index f63e5889e..95dfceaf3 100644 --- a/src/entt/resource/cache.hpp +++ b/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 +#include +#include +#include +#include #include #include #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. - * @tparam Loader Type of loader used to create the resources. + * @cond TURN_OFF_DOXYGEN + * Internal details not to be documented. */ -template -class resource_cache { - static_assert(std::is_base_of_v, "Invalid loader"); + +namespace internal { + +template +class resource_cache_iterator final { + template + friend class resource_cache_iterator; public: + using value_type = std::pair>; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::input_iterator_tag; + + resource_cache_iterator() ENTT_NOEXCEPT = default; + + resource_cache_iterator(const It iter) ENTT_NOEXCEPT + : it{iter} {} + + template>> + resource_cache_iterator(const resource_cache_iterator, Other> &other) ENTT_NOEXCEPT + : it{other.it} {} + + resource_cache_iterator &operator++() ENTT_NOEXCEPT { + return ++it, *this; + } + + resource_cache_iterator operator++(int) ENTT_NOEXCEPT { + resource_cache_iterator orig = *this; + return ++(*this), orig; + } + + resource_cache_iterator &operator--() ENTT_NOEXCEPT { + return --it, *this; + } + + resource_cache_iterator operator--(int) ENTT_NOEXCEPT { + resource_cache_iterator orig = *this; + return operator--(), orig; + } + + resource_cache_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { + it += value; + return *this; + } + + resource_cache_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { + resource_cache_iterator copy = *this; + return (copy += value); + } + + resource_cache_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { + return (*this += -value); + } + + resource_cache_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { + return (*this + -value); + } + + [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { + return {it[value].first, resource{it[value].second}}; + } + + [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { + return (*this)[0]; + } + + [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { + return operator*(); + } + + template + friend std::ptrdiff_t operator-(const resource_cache_iterator &, const resource_cache_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator==(const resource_cache_iterator &, const resource_cache_iterator &) ENTT_NOEXCEPT; + + template + friend bool operator<(const resource_cache_iterator &, const resource_cache_iterator &) ENTT_NOEXCEPT; + +private: + It it; +}; + +template +[[nodiscard]] std::ptrdiff_t operator-(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it - rhs.it; +} + +template +[[nodiscard]] bool operator==(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it == rhs.it; +} + +template +[[nodiscard]] bool operator!=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +template +[[nodiscard]] bool operator<(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return lhs.it < rhs.it; +} + +template +[[nodiscard]] bool operator>(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return rhs < lhs; +} + +template +[[nodiscard]] bool operator<=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs > rhs); +} + +template +[[nodiscard]] bool operator>=(const resource_cache_iterator &lhs, const resource_cache_iterator &rhs) ENTT_NOEXCEPT { + return !(lhs < rhs); +} + +} // namespace internal + +/** + * 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 +class resource_cache { + using alloc_traits = typename std::allocator_traits; + static_assert(std::is_same_v, "Invalid value type"); + using container_allocator = typename alloc_traits::template rebind_alloc>; + using container_type = dense_map, container_allocator>; + +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; + /*! @brief Constant input iterator type. */ + using const_iterator = internal::resource_cache_iterator; /*! @brief Default constructor. */ - resource_cache() = default; + resource_cache() + : resource_cache{loader_type{}} {} + + /** + * @brief Constructs an empty cache with a given allocator. + * @param allocator The allocator to use. + */ + explicit resource_cache(const allocator_type &allocator) + : resource_cache{loader_type{}, allocator} {} + + /** + * @brief Constructs an empty cache with a given allocator and loader. + * @param callable The loader to use. + * @param allocator The allocator to use. + */ + explicit resource_cache(const loader_type &callable, const allocator_type &allocator = allocator_type{}) + : pool{container_type{allocator}, callable} {} + + /*! @brief Default copy constructor. */ + resource_cache(const resource_cache &) = default; + + /** + * @brief Allocator-extended copy constructor. + * @param other The instance to copy from. + * @param allocator The allocator to use. + */ + resource_cache(const resource_cache &other, const allocator_type &allocator) + : pool{std::piecewise_construct, std::forward_as_tuple(other.pool.first(), allocator), std::forward_as_tuple(other.pool.second())} {} /*! @brief Default move constructor. */ resource_cache(resource_cache &&) = default; - /*! @brief Default move assignment operator. @return This cache. */ + /** + * @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. + * @brief Loads a resource, if its identifier does not exist. * - * In case an identifier isn't already present in the cache, it loads its - * resource and stores it aside for future uses. Arguments are forwarded - * directly to the loader in order to construct properly the requested - * resource. - * - * @note - * If the identifier is already present in the cache, this function does - * nothing and the arguments are simply discarded. + * Arguments are forwarded directly to the loader and _consumed_ only if the + * resource doesn't already exist. * * @warning - * If the resource cannot be loaded correctly, the returned handle will be + * If the resource isn't loaded correctly, the returned handle could be * invalid and any use of it will result in undefined behavior. * * @tparam Args Types of arguments to use to load the resource if required. * @param id Unique resource identifier. * @param args Arguments to use to load the resource if required. - * @return A handle for the given resource. + * @return A pair consisting of an iterator to the inserted element (or to + * the element that prevented the insertion) and a bool denoting whether the + * insertion took place. */ template - resource_handle load(const id_type id, Args &&...args) { - if(auto it = resources.find(id); it == resources.cend()) { - if(auto handle = temp(std::forward(args)...); handle) { - return (resources[id] = std::move(handle)); - } - } else { - return it->second; + std::pair load(const id_type id, Args &&...args) { + if(auto it = pool.first().find(id); it != pool.first().end()) { + return {it, false}; } - return {}; + return pool.first().emplace(id, pool.second()(std::forward(args)...)); } /** - * @brief Reloads a resource or loads it for the first time if not present. - * - * Equivalent to the following snippet (pseudocode): - * - * @code{.cpp} - * cache.discard(id); - * cache.load(id, args...); - * @endcode - * - * Arguments are forwarded directly to the loader in order to construct - * properly the requested resource. + * @brief Force loads a resource, if its identifier does not exist. + * @copydetails load + */ + template + std::pair force_load(const id_type id, Args &&...args) { + return {pool.first().insert_or_assign(id, pool.second()(std::forward(args)...)).first, true}; + } + + /** + * @brief Returns a handle for a given resource identifier. * * @warning - * If the resource cannot be loaded correctly, the returned handle will be - * invalid and any use of it will result in undefined behavior. - * - * @tparam Args Types of arguments to use to load the resource. - * @param id Unique resource identifier. - * @param args Arguments to use to load the resource. - * @return A handle for the given resource. - */ - template - resource_handle reload(const id_type id, Args &&...args) { - return (discard(id), load(id, std::forward(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. - */ - template - [[nodiscard]] resource_handle temp(Args &&...args) const { - return loader_type{}(std::forward(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 + * There is no guarantee that the returned handle is valid.
+ * 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 handle(const id_type id) const { - if(auto it = resources.find(id); it != resources.cend()) { - return it->second; + [[nodiscard]] resource operator[](const id_type id) const { + if(auto it = pool.first().find(id); it != pool.first().cend()) { + return resource{it->second}; } return {}; } - /*! @copydoc handle */ - [[nodiscard]] resource_handle handle(const id_type id) { - if(auto it = resources.find(id); it != resources.end()) { - return it->second; + /*! @copydoc operator[] */ + [[nodiscard]] resource operator[](const id_type id) { + if(auto it = pool.first().find(id); it != pool.first().end()) { + return resource{it->second}; } return {}; @@ -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. - * + * @brief Removes an element from a given position. + * @param pos An iterator to the element to remove. + * @return An iterator following the removed element. + */ + iterator erase(const_iterator pos) { + const auto it = pool.first().begin(); + return pool.first().erase(it + (pos - const_iterator{it})); + } + + /** + * @brief Removes the given elements from a cache. + * @param first An iterator to the first element of the range of elements. + * @param last An iterator past the last element of the range of elements. + * @return An iterator following the last removed element. + */ + iterator erase(const_iterator first, const_iterator last) { + const auto it = pool.first().begin(); + return pool.first().erase(it + (first - const_iterator{it}), it + (last - const_iterator{it})); + } + + /** + * @brief Removes the given elements from a cache. * @param id Unique resource identifier. + * @return Number of resources erased (either 0 or 1). */ - void discard(const id_type id) { - if(auto it = resources.find(id); it != resources.end()) { - resources.erase(it); - } + size_type erase(const id_type id) { + return pool.first().erase(id); } /** - * @brief 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.
- * 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); - * void(const entt::id_type, entt::resource_handle); - * @endcode - * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. + * @brief Returns the loader used to create resources. + * @return The loader used to create resources. */ - template - 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(curr->first); - } else if constexpr(std::is_invocable_v>) { - func(resource_handle{curr->second}); - } else { - func(curr->first, resource_handle{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.
- * 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); - * void(const entt::id_type, entt::resource_handle); - * @endcode - * - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. - */ - template - 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(curr->first); - } else if constexpr(std::is_invocable_v>) { - func(curr->second); - } else { - func(curr->first, curr->second); - } - } + [[nodiscard]] loader_type loader() const { + return pool.second(); } private: - dense_map, identity> resources; + compressed_pair pool; }; } // namespace entt diff --git a/src/entt/resource/fwd.hpp b/src/entt/resource/fwd.hpp index 2be3be94b..ed63da580 100644 --- a/src/entt/resource/fwd.hpp +++ b/src/entt/resource/fwd.hpp @@ -1,16 +1,18 @@ #ifndef ENTT_RESOURCE_FWD_HPP #define ENTT_RESOURCE_FWD_HPP +#include + namespace entt { -template +template +struct resource_loader; + +template, typename = std::allocator> class resource_cache; template -class resource_handle; - -template -class resource_loader; +class resource; } // namespace entt diff --git a/src/entt/resource/handle.hpp b/src/entt/resource/handle.hpp deleted file mode 100644 index 11a0af620..000000000 --- a/src/entt/resource/handle.hpp +++ /dev/null @@ -1,213 +0,0 @@ -#ifndef ENTT_RESOURCE_HANDLE_HPP -#define ENTT_RESOURCE_HANDLE_HPP - -#include -#include -#include -#include "../config/config.h" -#include "fwd.hpp" - -namespace entt { - -/** - * @brief Shared resource handle. - * - * A shared resource handle is a small class that wraps a resource and keeps it - * alive even if it's deleted from the cache. It can be either copied or - * moved. A handle shares a reference to the same resource with all the other - * handles constructed for the same identifier.
- * As a rule of thumb, resources should never be copied nor moved. Handles are - * the way to go to keep references to them. - * - * @tparam Resource Type of resource managed by a handle. - */ -template -class resource_handle { - /*! @brief Resource handles are friends with each other. */ - template - 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 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 - resource_handle(const resource_handle &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 && std::is_base_of_v>> - resource_handle(const resource_handle &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 && std::is_base_of_v>> - resource_handle(resource_handle &&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 - std::enable_if_t && std::is_base_of_v, resource_handle &> - operator=(const resource_handle &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 - std::enable_if_t && std::is_base_of_v, resource_handle &> - operator=(resource_handle &&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(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; -}; - -/** - * @brief Compares two handles. - * @tparam Res Type of resource managed by the first handle. - * @tparam Other Type of resource managed by the second handle. - * @param lhs A valid handle. - * @param rhs A valid handle. - * @return True if both handles refer to the same resource, false otherwise. - */ -template -[[nodiscard]] bool operator==(const resource_handle &lhs, const resource_handle &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 -[[nodiscard]] bool operator!=(const resource_handle &lhs, const resource_handle &rhs) ENTT_NOEXCEPT { - return !(lhs == rhs); -} - -} // namespace entt - -#endif diff --git a/src/entt/resource/loader.hpp b/src/entt/resource/loader.hpp new file mode 100644 index 000000000..25a7b34c6 --- /dev/null +++ b/src/entt/resource/loader.hpp @@ -0,0 +1,33 @@ +#ifndef ENTT_RESOURCE_LOADEr_HPP +#define ENTT_RESOURCE_LOADEr_HPP + +#include +#include +#include "fwd.hpp" + +namespace entt { + +/** + * @brief Transparent loader for shared resources. + * @tparam Type Type of resources created by the loader. + */ +template +struct resource_loader { + /*! @brief Result type. */ + using result_type = std::shared_ptr; + + /** + * @brief Constructs a shared pointer to a resource from its arguments. + * @tparam Args Types of arguments to use to construct the resource. + * @param args Parameters to use to construct the resource. + * @return A shared pointer to a resource of the given type. + */ + template + result_type operator()(Args &&...args) const { + return std::make_shared(std::forward(args)...); + } +}; + +} // namespace entt + +#endif diff --git a/src/entt/resource/resource.hpp b/src/entt/resource/resource.hpp new file mode 100644 index 000000000..ca19673cb --- /dev/null +++ b/src/entt/resource/resource.hpp @@ -0,0 +1,187 @@ +#ifndef ENTT_RESOURCE_RESOURCE_HPP +#define ENTT_RESOURCE_RESOURCE_HPP + +#include +#include +#include +#include "../config/config.h" +#include "fwd.hpp" + +namespace entt { + +/** + * @brief Basic resource handle. + * + * A handle wraps a resource and extends its lifetime. It also shares the same + * resource with all other handles constructed from the same element.
+ * As a rule of thumb, resources should never be copied nor moved. Handles are + * the way to go to push references around. + * + * @tparam Type Type of resource managed by a handle. + */ +template +class resource { + /*! @brief Resource handles are friends with each other. */ + template + friend class resource; + + template + static constexpr bool is_acceptable_v = !std::is_same_v && std::is_constructible_v; + +public: + /*! @brief Default constructor. */ + resource() ENTT_NOEXCEPT = 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 res) ENTT_NOEXCEPT + : value{std::move(res)} {} + + /*! @brief Default copy constructor. */ + resource(const resource &) ENTT_NOEXCEPT = default; + + /*! @brief Default move constructor. */ + resource(resource &&) ENTT_NOEXCEPT = default; + + /** + * @brief Aliasing constructor. + * @tparam Other Type of resource managed by the received handle. + * @param other The handle with which to share ownership information. + * @param res Unrelated and unmanaged resources. + */ + template + resource(const resource &other, Type &res) ENTT_NOEXCEPT + : value{other.value, std::addressof(res)} {} + + /** + * @brief Copy constructs a handle which shares ownership of the resource. + * @tparam Other Type of resource managed by the received handle. + * @param other The handle to copy from. + */ + template>> + resource(const resource &other) ENTT_NOEXCEPT + : value{other.value} {} + + /** + * @brief Move constructs a handle which takes ownership of the resource. + * @tparam Other Type of resource managed by the received handle. + * @param other The handle to move from. + */ + template>> + resource(resource &&other) ENTT_NOEXCEPT + : value{std::move(other.value)} {} + + /** + * @brief Default copy assignment operator. + * @return This resource handle. + */ + resource &operator=(const resource &) ENTT_NOEXCEPT = default; + + /** + * @brief Default move assignment operator. + * @return This resource handle. + */ + resource &operator=(resource &&) ENTT_NOEXCEPT = default; + + /** + * @brief Copy assignment operator from foreign handle. + * @tparam Other Type of resource managed by the received handle. + * @param other The handle to copy from. + * @return This resource handle. + */ + template + std::enable_if_t, resource &> + operator=(const resource &other) ENTT_NOEXCEPT { + value = other.value; + return *this; + } + + /** + * @brief Move assignment operator from foreign handle. + * @tparam Other Type of resource managed by the received handle. + * @param other The handle to move from. + * @return This resource handle. + */ + template + std::enable_if_t, resource &> + operator=(resource &&other) ENTT_NOEXCEPT { + value = std::move(other.value); + return *this; + } + + /** + * @brief Returns a reference to the managed resource. + * + * @warning + * The behavior is undefined if the handle doesn't contain a resource. + * + * @return A reference to the managed resource. + */ + [[nodiscard]] Type &operator*() const ENTT_NOEXCEPT { + return *value; + } + + /*! @copydoc operator* */ + [[nodiscard]] operator Type &() const ENTT_NOEXCEPT { + return *value; + } + + /** + * @brief Returns a pointer to the managed resource. + * @return A pointer to the managed resource. + */ + [[nodiscard]] Type *operator->() const ENTT_NOEXCEPT { + return value.get(); + } + + /** + * @brief Returns true if a handle contains a resource, false otherwise. + * @return True if the handle contains a resource, false otherwise. + */ + [[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT { + return static_cast(value); + } + + /** + * @brief Returns the number of handles pointing the same resource. + * @return The number of handles pointing the same resource. + */ + [[nodiscard]] long use_count() const ENTT_NOEXCEPT { + return value.use_count(); + } + +private: + std::shared_ptr value; +}; + +/** + * @brief Compares two handles. + * @tparam Res Type of resource managed by the first handle. + * @tparam Other Type of resource managed by the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return True if both handles refer to the same resource, false otherwise. + */ +template +[[nodiscard]] bool operator==(const resource &lhs, const resource &rhs) ENTT_NOEXCEPT { + return (std::addressof(*lhs) == std::addressof(*rhs)); +} + +/** + * @brief Compares two handles. + * @tparam Res Type of resource managed by the first handle. + * @tparam Other Type of resource managed by the second handle. + * @param lhs A valid handle. + * @param rhs A valid handle. + * @return False if both handles refer to the same registry, true otherwise. + */ +template +[[nodiscard]] bool operator!=(const resource &lhs, const resource &rhs) ENTT_NOEXCEPT { + return !(lhs == rhs); +} + +} // namespace entt + +#endif