Program Listing for File ResourceManager.hpp

Return to documentation for file (engine/include/Cacao/ResourceManager.hpp)

#pragma once

#include "Asset.hpp"
#include "Exceptions.hpp"
#include "DllHelper.hpp"
#include "Resource.hpp"
#include "Engine.hpp"

#include <memory>
#include <type_traits>
#include <string>
#include <typeindex>

namespace Cacao {
    //This is a helper type typedef because fully written out it's super long and nonsensical
    //To explain: This is the contained type of the returned unique_ptr type of a raw T (deref-ed for consistency)'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> && 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>(std::unique_ptr<LoaderIntermediate<T, R>> {}) } -> std::same_as<std::shared_ptr<R>>;
    };

    template<typename T, typename... Rs>
    concept MultiLoader = (Loader<T, Rs> && ...);

    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<BlobResource, 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([this, address]() -> std::shared_ptr<T> {
                //Check cache
                std::shared_ptr<Resource> maybeCached = CheckCache(address);
                if(maybeCached) {
                    //There is a cached resource
                    try {
                        //Try to cast the resource to the correct type and return it
                        return std::dynamic_pointer_cast<T>(maybeCached);
                    } catch(const std::bad_cast&) {
                        Check<BadTypeException>(false, "Resource exists in cache but is not of the requested type!");
                        return {};
                    }
                }

                //Resource was not in cache, we need to load it
                //Check for a valid loader
                Check<BadStateException>(IsLoaderRegistered(typeid(T)), "No resource loader configured for the requested type!");

                //Try to load the asset
                std::shared_ptr<Resource> res = InvokeLoader(typeid(T), address);
                try {
                    //Try to cast the resource to the correct type and return it
                    return std::dynamic_pointer_cast<T>(res);
                } catch(const std::bad_cast&) {
                    Check<BadTypeException>(false, "Resource was loade but the returned object is not of the requested type!");
                    return {};
                }

                return {};
            });
        }

        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;

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

        struct ErasedLoader {
            std::any loaderObj;
            std::function<std::shared_ptr<Resource>(const std::string&)> load;
        };

        template<typename T, typename R>
            requires Loader<std::remove_reference_t<T>, R>
        void _ConfigureResourceLoader(const T& loader) {
            Check<BadStateException>(!IsLoaderRegistered(typeid(R)), "A loader has already been configured for this type!");

            //Create erased loader object
            ErasedLoader el;
            el.loaderObj = loader;
            el.load = [loader](const std::string& addr) {
                std::unique_ptr<LoaderIntermediate<T, R>> intermediate = loader.template FetchData<R>(addr);
                return std::static_pointer_cast<Resource>(loader.template CreateResource<R>(std::move(intermediate)));
            };
        }
    };
}