added stuff for resource management
This commit is contained in:
183
src/entt/resource/cache.hpp
Normal file
183
src/entt/resource/cache.hpp
Normal file
@@ -0,0 +1,183 @@
|
||||
#ifndef ENTT_RESOURCE_CACHE_HPP
|
||||
#define ENTT_RESOURCE_CACHE_HPP
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include "../core/hashed_string.hpp"
|
||||
#include "handle.hpp"
|
||||
#include "loader.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Simple cache for resources of a given type.
|
||||
*
|
||||
* Minimal implementation of a cache for resources of a given type. It doesn't
|
||||
* offer much functionalities but it's suitable for small or medium sized
|
||||
* applications and can be freely inherited to add targeted functionalities for
|
||||
* large sized applications.
|
||||
*
|
||||
* @tparam Resource Type of resources managed by a cache.
|
||||
*/
|
||||
template<typename Resource>
|
||||
class ResourceCache {
|
||||
using container_type = std::unordered_map<HashedString::hash_type, std::shared_ptr<Resource>>;
|
||||
|
||||
public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = typename container_type::size_type;
|
||||
/*! @brief Type of resources managed by a cache. */
|
||||
using resource_type = HashedString;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
ResourceCache() = default;
|
||||
|
||||
/*! @brief Copying a cache isn't allowed. */
|
||||
ResourceCache(const ResourceCache &) noexcept = delete;
|
||||
/*! @brief Default move constructor. */
|
||||
ResourceCache(ResourceCache &&) noexcept = default;
|
||||
|
||||
/*! @brief Copying a cache isn't allowed. @return This cache. */
|
||||
ResourceCache & operator=(const ResourceCache &) noexcept = delete;
|
||||
/*! @brief Default move assignment operator. @return This cache. */
|
||||
ResourceCache & operator=(ResourceCache &&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Number of resources managed by a cache.
|
||||
* @return Number of resources currently stored.
|
||||
*/
|
||||
size_type size() const noexcept {
|
||||
return resources.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if a cache contains no resources, false otherwise.
|
||||
* @return True if the cache contains no resources, false otherwise.
|
||||
*/
|
||||
bool empty() const noexcept {
|
||||
return resources.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clears a cache and discards all its resources.
|
||||
*
|
||||
* Handles are not invalidated and the memory used by a resource isn't
|
||||
* freed as long as at least a handle keeps the resource itself alive.
|
||||
*/
|
||||
void clear() noexcept {
|
||||
resources.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Loads the resource that corresponds to the given identifier.
|
||||
*
|
||||
* In case an identifier isn't already present in the cache, it loads its
|
||||
* resource and stores it aside for future uses. Arguments are forwarded
|
||||
* directly to the loader in order to construct properly the requested
|
||||
* resource.
|
||||
*
|
||||
* @note
|
||||
* If the identifier is already present in the cache, this function does
|
||||
* nothing and the arguments are simply discarded.
|
||||
*
|
||||
* @tparam Loader Type of loader to use to load the resource if required.
|
||||
* @tparam Args Types of arguments to use to load the resource if required.
|
||||
* @param id Unique resource identifier.
|
||||
* @param args Arguments to use to load the resource if required.
|
||||
* @return True if the resource is ready to use, false otherwise.
|
||||
*/
|
||||
template<typename Loader, typename... Args>
|
||||
bool load(resource_type id, Args&&... args) {
|
||||
static_assert(std::is_base_of<ResourceLoader<Loader, Resource>, Loader>::value, "!");
|
||||
|
||||
bool loaded = true;
|
||||
|
||||
if(resources.find(id) == resources.cend()) {
|
||||
std::shared_ptr<Resource> resource = Loader{}.get(std::forward<Args>(args)...);
|
||||
loaded = (static_cast<bool>(resource) ? (resources[id] = std::move(resource), loaded) : false);
|
||||
}
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*
|
||||
* @tparam Loader Type of loader to use to load the resource.
|
||||
* @tparam Args Types of arguments to use to load the resource.
|
||||
* @param id Unique resource identifier.
|
||||
* @param args Arguments to use to load the resource.
|
||||
* @return True if the resource is ready to use, false otherwise.
|
||||
*/
|
||||
template<typename Loader, typename... Args>
|
||||
void reload(resource_type id, Args&&... args) {
|
||||
return (discard(id), load(id, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates a handle for the 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 ResourceHandle
|
||||
*
|
||||
* @param id Unique resource identifier.
|
||||
* @return A handle for the given resource.
|
||||
*/
|
||||
ResourceHandle<Resource> handle(resource_type id) const {
|
||||
auto it = resources.find(id);
|
||||
return { it == resources.end() ? nullptr : it->second };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a cache contains the given identifier.
|
||||
* @param id Unique resource identifier.
|
||||
* @return True if the cache contains the resource, false otherwise.
|
||||
*/
|
||||
bool contains(resource_type id) const noexcept {
|
||||
return !(resources.find(id) == resources.cend());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Discards the resource that corresponds to the given identifier.
|
||||
*
|
||||
* Handles are not invalidated and the memory used by the resource isn't
|
||||
* freed as long as at least a handle keeps the resource itself alive.
|
||||
*
|
||||
* @param id Unique resource identifier.
|
||||
*/
|
||||
void discard(resource_type id) noexcept {
|
||||
auto it = resources.find(id);
|
||||
|
||||
if(it != resources.end()) {
|
||||
resources.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
container_type resources;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_RESOURCE_CACHE_HPP
|
||||
115
src/entt/resource/handle.hpp
Normal file
115
src/entt/resource/handle.hpp
Normal file
@@ -0,0 +1,115 @@
|
||||
#ifndef ENTT_RESOURCE_HANDLE_HPP
|
||||
#define ENTT_RESOURCE_HANDLE_HPP
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
template<typename Resource>
|
||||
struct ResourceCache;
|
||||
|
||||
|
||||
/**
|
||||
* @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 ResourceHandle final {
|
||||
/*! @brief Resource handles are friends of their caches. */
|
||||
friend class ResourceCache<Resource>;
|
||||
|
||||
ResourceHandle(std::shared_ptr<Resource> res) noexcept
|
||||
: resource{std::move(res)}
|
||||
{}
|
||||
|
||||
public:
|
||||
/*! @brief Default copy constructor. */
|
||||
ResourceHandle(const ResourceHandle &) noexcept = default;
|
||||
/*! @brief Default move constructor. */
|
||||
ResourceHandle(ResourceHandle &&) noexcept = default;
|
||||
|
||||
/*! @brief Default copy assignment operator. @return This handle. */
|
||||
ResourceHandle & operator=(const ResourceHandle &) noexcept = default;
|
||||
/*! @brief Default move assignment operator. @return This handle. */
|
||||
ResourceHandle & operator=(ResourceHandle &&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @brief Gets a reference to the managed resource.
|
||||
*
|
||||
* @warning
|
||||
* The behavior is undefined if the handle doesn't contain a resource.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* handle is empty.
|
||||
*
|
||||
* @return A reference to the managed resource.
|
||||
*/
|
||||
const Resource & get() const noexcept {
|
||||
assert(static_cast<bool>(resource));
|
||||
return *resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Casts a handle and gets a reference to the managed resource.
|
||||
*
|
||||
* @warning
|
||||
* The behavior is undefined if the handle doesn't contain a resource.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* handle is empty.
|
||||
*/
|
||||
inline operator const Resource & () const noexcept { return get(); }
|
||||
|
||||
/**
|
||||
* @brief Dereferences a handle to obtain the managed resource.
|
||||
*
|
||||
* @warning
|
||||
* The behavior is undefined if the handle doesn't contain a resource.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* handle is empty.
|
||||
*
|
||||
* @return A reference to the managed resource.
|
||||
*/
|
||||
inline const Resource & operator *() const noexcept { return get(); }
|
||||
|
||||
/**
|
||||
* @brief Gets a pointer to the managed resource from a handle .
|
||||
*
|
||||
* @warning
|
||||
* The behavior is undefined if the handle doesn't contain a resource.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* handle is empty.
|
||||
*
|
||||
* @return A pointer to the managed resource or `nullptr` if the handle
|
||||
* contains no resource at all.
|
||||
*/
|
||||
inline const Resource * operator ->() const noexcept {
|
||||
assert(static_cast<bool>(resource));
|
||||
return resource.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if the handle contains a resource, false otherwise.
|
||||
*/
|
||||
explicit operator bool() const { return static_cast<bool>(resource); }
|
||||
|
||||
private:
|
||||
std::shared_ptr<Resource> resource;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_RESOURCE_HANDLE_HPP
|
||||
62
src/entt/resource/loader.hpp
Normal file
62
src/entt/resource/loader.hpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#ifndef ENTT_RESOURCE_LOADER_HPP
|
||||
#define ENTT_RESOURCE_LOADER_HPP
|
||||
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
template<typename Resource>
|
||||
struct ResourceCache;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Base class for resource loaders.
|
||||
*
|
||||
* Resource loaders must inherit from this class and stay true to the CRTP
|
||||
* idiom. Moreover, a resource loader must expose a public, const member
|
||||
* function named `load` that accepts a variable number of arguments and return
|
||||
* a shared pointer to the resource just created.<br/>
|
||||
* As an example:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* struct MyResource {};
|
||||
*
|
||||
* struct MyLoader: entt::ResourceLoader<MyLoader, MyResource> {
|
||||
* std::shared_ptr<MyResource> load(int) const {
|
||||
* // use the integer value somehow
|
||||
* return std::make_shared<MyResource>();
|
||||
* }
|
||||
* };
|
||||
* @endcode
|
||||
*
|
||||
* In general, resource loaders should not have a state or retain data of any
|
||||
* type. They should let the cache manage their resources instead.
|
||||
*
|
||||
* @note
|
||||
* Base class and CRTP idiom aren't strictly required with the current
|
||||
* implementation. One could argue that a cache can easily work with loaders of
|
||||
* any type. However, future changes won't be breaking ones by forcing the use
|
||||
* of a base class today and that's why the model is already in its place.
|
||||
*
|
||||
* @tparam Loader Type of the derived class.
|
||||
* @tparam Resource Type of resource for which to use the loader.
|
||||
*/
|
||||
template<typename Loader, typename Resource>
|
||||
class ResourceLoader {
|
||||
/*! @brief Resource loaders are friends of their caches. */
|
||||
friend class ResourceCache<Resource>;
|
||||
|
||||
template<typename... Args>
|
||||
std::shared_ptr<Resource> get(Args&&... args) const {
|
||||
return static_cast<const Loader *>(this)->load(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // ENTT_RESOURCE_LOADER_HPP
|
||||
80
test/entt/resource/resource.cpp
Normal file
80
test/entt/resource/resource.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/resource/cache.hpp>
|
||||
|
||||
struct Resource { const int value; };
|
||||
|
||||
struct Loader: entt::ResourceLoader<Loader, Resource> {
|
||||
std::shared_ptr<Resource> load(int value) const {
|
||||
return std::shared_ptr<Resource>(new Resource{ value });
|
||||
}
|
||||
};
|
||||
|
||||
struct BrokenLoader: entt::ResourceLoader<BrokenLoader, Resource> {
|
||||
std::shared_ptr<Resource> load(int) const {
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(ResourceCache, Functionalities) {
|
||||
entt::ResourceCache<Resource> cache;
|
||||
|
||||
constexpr auto hs1 = entt::HashedString{"res1"};
|
||||
constexpr auto hs2 = entt::HashedString{"res2"};
|
||||
|
||||
ASSERT_EQ(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
||||
ASSERT_TRUE(cache.empty());
|
||||
ASSERT_FALSE(cache.contains(hs1));
|
||||
ASSERT_FALSE(cache.contains(hs2));
|
||||
|
||||
ASSERT_FALSE(cache.load<BrokenLoader>(hs1, 42));
|
||||
|
||||
ASSERT_EQ(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
||||
ASSERT_TRUE(cache.empty());
|
||||
ASSERT_FALSE(cache.contains(hs1));
|
||||
ASSERT_FALSE(cache.contains(hs2));
|
||||
|
||||
ASSERT_TRUE(cache.load<Loader>(hs1, 42));
|
||||
|
||||
ASSERT_NE(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
||||
ASSERT_FALSE(cache.empty());
|
||||
ASSERT_TRUE(cache.contains(hs1));
|
||||
ASSERT_FALSE(cache.contains(hs2));
|
||||
ASSERT_EQ((*cache.handle(hs1)).value, 42);
|
||||
|
||||
ASSERT_TRUE(cache.load<Loader>(hs2, 42));
|
||||
|
||||
ASSERT_NE(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
||||
ASSERT_FALSE(cache.empty());
|
||||
ASSERT_TRUE(cache.contains(hs1));
|
||||
ASSERT_TRUE(cache.contains(hs2));
|
||||
ASSERT_EQ((*cache.handle(hs1)).value, 42);
|
||||
ASSERT_EQ(cache.handle(hs2)->value, 42);
|
||||
|
||||
ASSERT_NO_THROW(cache.discard(hs1));
|
||||
|
||||
ASSERT_FALSE(cache.contains(hs1));
|
||||
ASSERT_TRUE(cache.contains(hs2));
|
||||
ASSERT_EQ(cache.handle(hs2)->value, 42);
|
||||
|
||||
ASSERT_TRUE(cache.load<Loader>(hs1, 42));
|
||||
ASSERT_NO_THROW(cache.clear());
|
||||
|
||||
ASSERT_EQ(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
||||
ASSERT_TRUE(cache.empty());
|
||||
ASSERT_FALSE(cache.contains(hs1));
|
||||
ASSERT_FALSE(cache.contains(hs2));
|
||||
|
||||
ASSERT_TRUE(cache.load<Loader>(hs1, 42));
|
||||
|
||||
ASSERT_NE(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
||||
ASSERT_FALSE(cache.empty());
|
||||
ASSERT_TRUE(cache.handle(hs1));
|
||||
ASSERT_FALSE(cache.handle(hs2));
|
||||
|
||||
ASSERT_TRUE(cache.handle(hs1));
|
||||
ASSERT_EQ(&cache.handle(hs1).get(), &static_cast<const Resource &>(cache.handle(hs1)));
|
||||
ASSERT_NO_THROW(cache.clear());
|
||||
|
||||
ASSERT_EQ(cache.size(), entt::ResourceCache<Resource>::size_type{});
|
||||
ASSERT_TRUE(cache.empty());
|
||||
}
|
||||
Reference in New Issue
Block a user