Program Listing for File ResourceManager.hpp
↰ Return to documentation for file (engine/include/Cacao/ResourceManager.hpp)
#pragma once
#include "Asset.hpp"
#include "Cubemap.hpp"
#include "Model.hpp"
#include "World.hpp"
#include "Material.hpp"
#include "Sound.hpp"
#include "Tex2D.hpp"
#include "Exceptions.hpp"
#include "DllHelper.hpp"
#include "Resource.hpp"
#include "Engine.hpp"
#include "Shader.hpp"
#include "Mesh.hpp"
#include <exception>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include <string>
#include <typeindex>
#include <utility>
namespace Cacao {
//This is a helper type typedef because fully written out it's super long and nonsensical
//To explain: Given a type T (deref'ed for consistency), LoaderIntermediate is the U in the unique_ptr<U> return value of T's FetchData function
template<typename T, typename R>
using LoaderIntermediate = decltype(std::declval<std::remove_reference_t<T>>().template FetchData<R>(std::declval<std::string>()))::element_type;
template<typename T, typename R>
concept Loader = std::is_base_of_v<Resource, R> && (!std::is_same_v<Asset, R>) && requires(T obj, const std::string& addr) {
{ obj.template FetchData<R>(addr) } -> std::same_as<std::unique_ptr<LoaderIntermediate<T, R>>>;
{ obj.template CreateResource<R>(addr, std::unique_ptr<LoaderIntermediate<T, R>> {}) } -> std::same_as<std::shared_ptr<R>>;
};
template<typename T, typename... Rs>
concept MultiLoader = (Loader<T, Rs> && ...);
//Declare explicitly allowed specializations
template<>
CACAO_API bool Resource::ValidateResourceAddr<Tex2D>(const std::string& addr);
template<>
CACAO_API bool Resource::ValidateResourceAddr<Mesh>(const std::string& addr);
template<>
CACAO_API bool Resource::ValidateResourceAddr<Model>(const std::string& addr);
template<>
CACAO_API bool Resource::ValidateResourceAddr<Cubemap>(const std::string& addr);
template<>
CACAO_API bool Resource::ValidateResourceAddr<Sound>(const std::string& addr);
template<>
CACAO_API bool Resource::ValidateResourceAddr<Shader>(const std::string& addr);
template<>
CACAO_API bool Resource::ValidateResourceAddr<Material>(const std::string& addr);
template<>
CACAO_API bool Resource::ValidateResourceAddr<World>(const std::string& addr);
template<>
CACAO_API bool Resource::ValidateResourceAddr<BlobResource>(const std::string& addr);
class CACAO_API ResourceManager {
public:
static ResourceManager& Get();
ResourceManager(const ResourceManager&) = delete;
ResourceManager(ResourceManager&&) = delete;
ResourceManager& operator=(const ResourceManager&) = delete;
ResourceManager& operator=(ResourceManager&&) = delete;
template<typename T>
requires std::is_base_of_v<Resource, T> && (!std::is_same_v<Asset, T>)
exathread::Future<std::shared_ptr<T>> Load(const std::string& address) {
//Validate the address
Check<BadValueException>(Resource::ValidateResourceAddr<T>(address), "Cannot load a resource from a malformed address string!");
//Run load operation asynchronously
return Engine::Get().GetThreadPool()->submit(std::bind(&ResourceManager::_AsyncLoadOp<T>, this, std::placeholders::_1), address);
}
template<typename T, typename... Types>
requires MultiLoader<std::remove_reference_t<T>, Types...>
void ConfigureResourceLoader(T&& loader) {
//For those unaware, this is a fold expression
//What this does is it will run _ConfigureResourceLoader for each type in the Types pack
((_ConfigureResourceLoader<T, Types>(std::move(loader))), ...);
}
struct Impl;
private:
std::unique_ptr<Impl> impl;
friend class ImplAccessor;
friend class Engine;
ResourceManager();
~ResourceManager();
std::shared_ptr<Resource> CheckCache(const std::string& addr);
bool IsLoaderRegistered(std::type_index tp);
std::shared_ptr<Resource> InvokeLoader(std::type_index tp, const std::string& addr);
exathread::ValueTask<std::shared_ptr<Resource>> _AsyncLoadOpImpl(std::string addr, std::type_index tp);
void SetupBuiltins();
void CleanupBuiltins();
std::shared_ptr<Mesh> LoadBuiltinMesh(const std::string& id);
std::shared_ptr<Shader> LoadBuiltinShader(const std::string& id);
template<typename T>
requires std::is_base_of_v<Resource, T> && (!std::is_same_v<Asset, T>)
exathread::ValueTask<std::shared_ptr<T>> _AsyncLoadOp(std::string address) {
//Check if this is a built-in resource
if(address.starts_with("a:builtin_") || address.starts_with("a:internal_")) {
if constexpr(std::is_same_v<T, Mesh>) co_return LoadBuiltinMesh(address);
if constexpr(std::is_same_v<T, Shader>) co_return LoadBuiltinShader(address);
Check<NonexistentValueException>(false, "No built-in resource found!");
}
//Return casted impl
exathread::Future<std::shared_ptr<Resource>> fut = Engine::Get().GetThreadPool()->submit(std::bind(&ResourceManager::_AsyncLoadOpImpl, this, std::placeholders::_1, std::placeholders::_2),
address, std::type_index {typeid(T)});
co_await exathread::yieldUntilComplete(fut);
std::shared_ptr<Resource> result = *fut;
try {
co_return std::dynamic_pointer_cast<T>(result);
} catch(const std::bad_cast&) {
Check<BadTypeException>(false, "Resource exists, but is not of the requested type!");
throw std::runtime_error("UNREACHABLE CODE!!! HOW DID YOU GET HERE?!");//This will never be reached because of the Check call, but the compiler doesn't know what Check does, so we have to spell it out like it's 3
}
}
struct ErasedLoader {
void* loaderObj = nullptr;
std::function<std::shared_ptr<Resource>(ErasedLoader&, const std::string&)> _load;
std::function<void(void*)> _destroy;
std::shared_ptr<Resource> load(const std::string& addr) {
return _load(*this, addr);
}
ErasedLoader() = default;
ErasedLoader(const ErasedLoader&) = delete;
ErasedLoader(ErasedLoader&& o)
: loaderObj(std::exchange(o.loaderObj, nullptr)), _load(std::move(o._load)), _destroy(std::move(o._destroy)) {}
ErasedLoader& operator=(const ErasedLoader&) = delete;
ErasedLoader& operator=(ErasedLoader&& o) {
if(this != &o) {
loaderObj = std::exchange(o.loaderObj, nullptr);
_load = std::move(o._load);
_destroy = std::move(o._destroy);
}
return *this;
}
~ErasedLoader() {
if(loaderObj) _destroy(loaderObj);
loaderObj = nullptr;
}
};
void RegisterLoader(std::type_index tp, ErasedLoader&& el);
template<typename T, typename R>
requires Loader<std::remove_reference_t<T>, R>
void _ConfigureResourceLoader(T&& loader) {
Check<BadStateException>(!IsLoaderRegistered(typeid(R)), "A loader has already been configured for this type!");
//Create erased loader object
ErasedLoader el;
el.loaderObj = (void*)new T(std::move(loader));
el._load = [](ErasedLoader& el, const std::string& addr) {
try {
T* loader = static_cast<T*>(el.loaderObj);
std::unique_ptr<LoaderIntermediate<T, R>> intermediate = loader->template FetchData<R>(addr);
return std::static_pointer_cast<Resource>(loader->template CreateResource<R>(addr, std::move(intermediate)));
} catch(const Exception&) {
std::rethrow_exception(std::current_exception());
} catch(const std::exception& e) {
Check<ExternalException>(false, e.what());
throw std::runtime_error("UNREACHABLE CODE!!! HOW DID YOU GET HERE?!");//This will never be reached because of the Check call, but the compiler doesn't know what Check does, so we have to spell it out like it's 3
}
};
el._destroy = [](void* obj) {
delete static_cast<T*>(obj);
};
//Add loader to list
RegisterLoader(typeid(R), std::move(el));
}
};
}