resource: fully review the resource module

This commit is contained in:
Michele Caini
2022-03-31 15:43:07 +02:00
parent b63aebfdaf
commit c54409bdfb
8 changed files with 558 additions and 403 deletions

View File

@@ -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>

4
TODO
View File

@@ -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)

View File

@@ -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"

View File

@@ -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.
* @tparam Loader Type of loader used to create the resources.
* @cond TURN_OFF_DOXYGEN
* Internal details not to be documented.
*/
template<typename Resource, typename Loader>
class resource_cache {
static_assert(std::is_base_of_v<Resource, typename Loader::value_type>, "Invalid loader");
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 Type, typename Loader, typename Allocator>
class resource_cache {
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.
* @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<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 {};
return pool.first().emplace(id, pool.second()(std::forward<Args>(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<typename... 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 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<typename... Args>
resource_handle<resource_type> reload(const id_type id, Args &&...args) {
return (discard(id), load(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 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... Args>
[[nodiscard]] resource_handle<resource_type> temp(Args &&...args) const {
return loader_type{}(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
* 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.
*
* @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.<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 Returns the loader used to create resources.
* @return The loader used to create resources.
*/
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);
}
}
[[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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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