Vulkan 学习笔记 05

本章的主要内容是关于资源管理,设计了一个基于句柄的资源管理器。为什么使用句柄呢,因为这样可以隔离实际资源的内存指针与用户接触的可能,尽量保证数据安全性。由于资源统一管理,既可以在这里接入统一的内存管理,避免四处修改代码,也可以做资源利用统计,加入一些缓存算法,提高效率。不过这个是第一次设计,肯定会有许多不足,后续应该会逐步优化。

为了方便使用,尽量设计了统一的接口。

#pragma once
#include "runtime/core/base/hash.h"

#include <atomic>
#include <cstdint>
#include <limits>

namespace ArchViz
{
    using ResourceTypeId = uint16_t;
    using MagicId        = uint16_t;
    using ResourceId     = uint64_t;

    constexpr MagicId        k_magic_id                 = 0x1234;
    constexpr ResourceTypeId k_invalid_resource_type_id = std::numeric_limits<uint16_t>::max();
    constexpr ResourceId     k_invalid_resource_id      = std::numeric_limits<uint64_t>::max();

    class ResourceTypeIdAllocator
    {
    public:
        static ResourceTypeId alloc();

    private:
        static std::atomic<ResourceTypeId> m_next_id;
    };

    class ResourceIdAllocator
    {
    public:
        static ResourceId alloc();

    private:
        static std::atomic<ResourceId> m_next_id;
    };

    struct ResHandle
    {
        MagicId        magic {k_magic_id};
        ResourceTypeId type;
        ResourceId     index;
    };

    static ResHandle k_invalid_res_handle {0, k_invalid_resource_type_id, k_invalid_index};

    template<>
    inline bool is_valid_handle(const ResHandle& handle)
    {
        return handle.index != k_invalid_index && handle.type != k_invalid_resource_type_id && handle.magic == k_magic_id;
    }

    constexpr uint32_t k_max_resource_count = 1024;
    constexpr size_t   k_max_resource_size  = 1024 * 1024 * 40; // 40 Mb
} // namespace ArchViz

namespace std
{
    template<>
    struct hash<ArchViz::ResHandle>
    {
        size_t operator()(ArchViz::ResHandle const& handle) const
        {
            size_t handle_hash = 0;
            ArchViz::hash_combine(handle_hash, handle.magic, handle.type, handle.index);
            return handle_hash;
        }
    };
} // namespace std

#include "runtime/resource/resource_manager/resource_handle.h"

#include "runtime/core/base/macro.h"

namespace ArchViz
{
    std::atomic<ResourceTypeId> ResourceTypeIdAllocator::m_next_id {0};
    std::atomic<ResourceId>     ResourceIdAllocator::m_next_id {0};

    ResourceTypeId ResourceTypeIdAllocator::alloc()
    {
        std::atomic<ResourceTypeId> new_object_ret = m_next_id.load();
        m_next_id++;
        if (m_next_id >= k_invalid_resource_type_id)
        {
            LOG_FATAL("gobject id overflow");
        }
        return new_object_ret;
    }

    ResourceId ResourceIdAllocator::alloc()
    {
        std::atomic<ResourceId> new_object_ret = m_next_id.load();
        m_next_id++;
        if (m_next_id >= k_invalid_resource_id)
        {
            LOG_FATAL("gobject id overflow");
        }
        return new_object_ret;
    }
} // namespace ArchViz
#pragma once
#include "runtime/core/base/macro.h"
#include "runtime/resource/resource_manager/resource_handle.h"

#include <array>
#include <atomic>
#include <cassert>
#include <memory>
#include <unordered_map>

namespace ArchViz
{
    // The virtual inheritance of IComponentArray is unfortunate but, as far as I can tell, unavoidable. As seen later, we'll have a list of every ComponentArray (one per component type), and we need
    // to notify all of them when an entity is destroyed so that it can remove the entity's data if it exists. The only way to keep a list of multiple templated types is to keep a list of their common
    // interface so that we can call EntityDestroyed() on all of them.
    class IResourceArray
    {
    public:
        virtual ~IResourceArray() = default;

        virtual void handleDestroyed(const ResourceId& res) = 0;
    };

    template<typename T>
    class ResourceArray : public IResourceArray
    {
    public:
        explicit ResourceArray(size_t count, size_t size);
        virtual ~ResourceArray() = default;

        std::weak_ptr<T> getData(const ResourceId& handle);

        void insertData(const ResourceId& handle, std::shared_ptr<T> resource, size_t size);
        void removeData(const ResourceId& handle);
        void handleDestroyed(const ResourceId& handle) override;

    private:
        size_t m_max_count;
        size_t m_max_size;

        std::vector<std::shared_ptr<T>> m_resources {};
        std::vector<size_t>             m_resource_sizes {};

        std::unordered_map<ResourceId, size_t> m_handle_to_index {};
        std::unordered_map<size_t, ResourceId> m_index_to_handle {};
        size_t                                 m_array_size {0};
        size_t                                 m_content_size {0};
    };
} // namespace ArchViz
#include "runtime/resource/resource_manager/resource_array.h"

namespace ArchViz
{
    template<typename T>
    ResourceArray<T>::ResourceArray(size_t count, size_t size) : m_max_count(count), m_max_size(size)
    {
        m_resources.resize(m_max_count);
        m_resource_sizes.resize(m_max_count);
    }

    template<typename T>
    void ResourceArray<T>::insertData(const ResourceId& handle, std::shared_ptr<T> resource, size_t size)
    {
        ASSERT(m_handle_to_index.count(handle) == 0 && "Component added to same entity more than once.");
        if (m_content_size >= m_max_size)
        {
            LOG_WARN("Resource Array size exceed max content size: {}", m_max_size);
        }
        if (m_array_size >= m_max_size)
        {
            LOG_ERROR("Resource Array size exceed max array size: {}", m_array_size);
        }
        // Put new entry at end
        size_t new_index             = m_array_size;
        m_handle_to_index[handle]    = new_index;
        m_index_to_handle[new_index] = handle;
        m_resources[new_index]       = resource;
        m_resource_sizes[new_index]  = size;
        m_content_size += size;
        m_array_size += 1;
    }

    template<typename T>
    std::weak_ptr<T> ResourceArray<T>::getData(const ResourceId& handle)
    {
        // we can get non-existent resource with nullptr
        // ASSERT(m_handle_to_index.count(handle) != 0 && "Retrieving non-existent resource.");
        return m_resources[m_handle_to_index[handle]];
    }

    template<typename T>
    void ResourceArray<T>::removeData(const ResourceId& handle)
    {
        ASSERT(m_handle_to_index.count(handle) != 0 && "Removing non-existent resource.");

        // Copy element at end into deleted element's place to maintain density
        // with smart pointer to avoid real memory copy
        size_t index_of_removed_handle       = m_handle_to_index[handle];
        size_t index_of_last_element         = m_array_size - 1;
        m_resources[index_of_removed_handle] = m_resources[index_of_last_element];
        m_resources[index_of_last_element]   = nullptr;

        // Update map to point to moved spot
        ResHandle& handle_of_last_element          = m_index_to_handle[index_of_last_element];
        m_handle_to_index[index_of_last_element]   = index_of_removed_handle;
        m_index_to_handle[index_of_removed_handle] = handle_of_last_element;

        // update size record
        m_content_size -= m_resource_sizes[index_of_removed_handle];
        m_resource_sizes[index_of_removed_handle] = m_resource_sizes[index_of_last_element];
        m_resource_sizes[index_of_last_element]   = 0;

        // remove unused record
        m_handle_to_index.erase(handle);
        m_index_to_handle.erase(index_of_last_element);
        --m_array_size;
    }

    template<typename T>
    void ResourceArray<T>::handleDestroyed(const ResourceId& handle)
    {
        if (m_handle_to_index.count(handle) != 0)
        {
            removeData(handle);
        }
    }
} // namespace ArchViz

#pragma once
#include <memory>
#include <string>
#include <utility>

namespace ArchViz
{
    class ILoader
    {
    public:
        virtual ~ILoader() = default;
    };

    // create a resource from CI
    template<typename T, typename CI>
    class Loader : public ILoader
    {
    public:
        virtual ~Loader() = default;

        virtual std::pair<std::shared_ptr<T>, size_t> createResource(const CI& create_info)  = 0;
        virtual std::pair<std::shared_ptr<T>, size_t> createResource(const std::string& uri) = 0;
    };
} // namespace ArchViz

// https://floooh.github.io/2018/06/17/handles-vs-pointers.html
// https://giordi91.github.io/post/resourcesystem/
#pragma once
#include "runtime/core/base/instanceof.h"
#include "runtime/core/base/macro.h"

#include "runtime/resource/resource_manager/loader/loader.h"

#include "runtime/resource/resource_manager/resource_array.h"
#include "runtime/resource/resource_manager/resource_handle.h"

#include <memory>
#include <string>
#include <type_traits>
#include <typeinfo>
#include <unordered_map>

namespace ArchViz
{
    struct ResourceManagerCreateInfo
    {
        std::unordered_map<const char*, size_t> resource_max_counts;
        std::unordered_map<const char*, size_t> resource_max_sizes;
    };

    // a handle based simple resource manager
    class ResourceManager
    {
    public:
        void initialize();
        void clear();

        template<typename T>
        void registerResourceType();

        template<typename T>
        ResourceTypeId getResourceType() const;
        const char*    getResourceTypeName(ResourceTypeId type) const;

        template<typename T, typename L>
        void registerResourceLoader();

        template<typename T>
        ResHandle createHandle();

        template<typename T, typename CI>
        std::weak_ptr<T> loadResource(const std::string& uri);

        template<typename T, typename CI>
        std::weak_ptr<T> loadResource(const std::string& uri, const CI& create_info);

        template<typename T>
        void addResource(const ResHandle& handle, std::shared_ptr<T> res);

        template<typename T>
        void removeResource(const ResHandle& handle);

        template<typename T>
        void removeResource(const std::string& uri);

        template<typename T>
        std::weak_ptr<T> getResource(const ResHandle& handle);

    private:
        template<typename T>
        std::shared_ptr<ResourceArray<T>> getResourceArray()
        {
            const char* type_name = typeid(T).name();
            ASSERT(m_resource_types.count(type_name) != 0 && "Component not registered before use.");
            return std::static_pointer_cast<ResourceArray<T>>(m_resource_arrays[type_name]);
        }

        template<typename T>
        std::shared_ptr<ResourceArray<T>> getResourceArray(ResourceTypeId type)
        {
            ASSERT(m_resource_id_arrays.count(type) != 0 && "Component not registered before use.");
            return std::static_pointer_cast<ResourceArray<T>>(m_resource_id_arrays[type]);
        }

    private:
        std::unordered_map<const char*, size_t>                             m_resource_max_counts;  // software constraints
        std::unordered_map<const char*, size_t>                             m_resource_max_sizes;   // hardware constraints
        std::unordered_map<const char*, ResourceTypeId>                     m_resource_types;       // resource type map
        std::unordered_map<ResourceTypeId, const char*>                     m_resource_types_inv;   // resource type map
        std::unordered_map<const char*, std::shared_ptr<IResourceArray>>    m_resource_arrays;      // resource storage
        std::unordered_map<ResourceTypeId, std::shared_ptr<IResourceArray>> m_resource_id_arrays;   // resource storage
        std::unordered_map<const char*, std::shared_ptr<ILoader>>           m_resource_loaders;     // typeid -> loader
        std::unordered_map<std::string, ResHandle>                          m_resource_handles;     // uri -> handle
        std::unordered_map<ResHandle, std::string>                          m_resource_handles_inv; // uri -> handle
    };
} // namespace ArchViz

#include "runtime/resource/resource_manager/resource_manager.h"

#include "runtime/resource/resource_manager/loader/material_loader.h"
#include "runtime/resource/resource_manager/loader/obj_loader.h"
#include "runtime/resource/resource_manager/loader/texture_loader.h"

#include "runtime/resource/res_type/data/material_data.h"
#include "runtime/resource/res_type/data/mesh_data.h"

#include "runtime/core/base/hash.h"
#include "runtime/core/base/macro.h"

namespace ArchViz
{
    void ResourceManager::initialize()
    {
        registerResourceType<MeshData>();
        registerResourceType<TextureData>();
        registerResourceType<MaterialData>();

        registerResourceLoader<MeshData, ObjLoader>();
        registerResourceLoader<TextureData, TextureLoader>();
        registerResourceLoader<MaterialData, MaterialLoader>();
    }

    void ResourceManager::clear()
    {
        m_resource_max_counts.clear();
        m_resource_max_sizes.clear();
        m_resource_types.clear();
        m_resource_types_inv.clear();
        m_resource_arrays.clear();
        m_resource_id_arrays.clear();
        m_resource_loaders.clear();
        m_resource_handles.clear();
    }

    template<typename T>
    void ResourceManager::registerResourceType()
    {
        const std::type_info& type = typeid(T);
        if (m_resource_types.count(type.name()) == 0)
        {
            // TODO : read from config file
            ResourceTypeId id              = ResourceTypeIdAllocator::alloc();
            m_resource_types[type.name()]  = id;
            m_resource_types_inv[id]       = type.name();
            m_resource_arrays[type.name()] = std::make_shared<ResourceArray<T>>(k_max_resource_count, k_max_resource_size);
            m_resource_id_arrays[id]       = m_resource_arrays[type.name()];
            // TODO : use this to trigger memory collect
            m_resource_max_counts[type.name()] = k_max_resource_count;
            m_resource_max_sizes[type.name()]  = k_max_resource_size;
        }
        else
        {
            LOG_WARN("Registering resource type: {} more than once.", type.name());
        }
    }

    template<typename T>
    ResourceTypeId ResourceManager::getResourceType() const
    {
        const std::type_info& type = typeid(T);
        if (m_resource_types.count(type.name()) != 0)
        {
            const std::type_info& type = typeid(T);
            return m_resource_types[type.name()];
        }
        else
        {
            return k_invalid_resource_type_id;
        }
    }

    const char* ResourceManager::getResourceTypeName(ResourceTypeId type) const
    {
        static const char* invalid_name = "invalid_type_id";
        if (m_resource_types_inv.count(type) == 0)
        {
            LOG_ERROR("try to get invalid type id's name: {}", type);
            return invalid_name;
        }
        else
        {
            // cannot use [] operator here
            auto it = m_resource_types_inv.find(type);
            return it->second;
        }
    }

    template<typename T, typename L>
    void ResourceManager::registerResourceLoader()
    {
        // one resource type can only have one loader now
        const std::type_info& type = typeid(T);

        if (m_resource_loaders.count(type.name()) == 0)
        {
            std::shared_ptr<L> loader = std::make_shared<L>();

            // must be a ILoader class
            bool is_loader = std::is_base_of<ILoader, L>::value;
            ASSERT(is_loader && "not a valid loader class");

            m_resource_loaders[type.name()] = loader;
        }
    }

    template<typename T>
    ResHandle ResourceManager::createHandle()
    {
        const auto& type    = typeid(T);
        auto        type_id = m_resource_types[type.name()];
        auto        index   = ResourceIdAllocator::alloc();
        return {k_magic_id, type_id, index};
    }

    template<typename T>
    void ResourceManager::addResource(const ResHandle& handle, std::shared_ptr<T> res)
    {
        // Add a component to the array for an handle
        getResourceArray<T>(handle.type)->insertData(handle.index, res);
    }

    template<typename T>
    void ResourceManager::removeResource(const ResHandle& handle)
    {
        if (m_resource_handles_inv.count(handle) == 0)
        {
            LOG_WARN("remove non-exist resource handle: {}", handle.index);
            return;
        }

        // Remove a component from the array for an handle
        getResourceArray<T>(handle.type)->removeData(handle.index);

        std::string name = m_resource_handles_inv[handle];
        m_resource_handles_inv.erase(handle);
        m_resource_handles.erase(name);
    }

    template<typename T>
    void ResourceManager::removeResource(const std::string& uri)
    {
        if (m_resource_handles.count(uri) == 0)
        {
            LOG_WARN("remove non-exist resource: {}", uri);
            return;
        }

        auto handle = m_resource_handles[uri];

        // Remove a component from the array for an handle
        getResourceArray<T>(handle.type)->removeData(handle.index);

        m_resource_handles_inv.erase(handle);
        m_resource_handles.erase(uri);
    }

    template<typename T>
    std::weak_ptr<T> ResourceManager::getResource(const ResHandle& handle)
    {
        // Get a reference to a component from the array for an handle
        return getResourceArray<T>(handle.type)->getData(handle.index);
    }

    template<typename T, typename CI>
    std::weak_ptr<T> ResourceManager::loadResource(const std::string& uri)
    {
        if (m_resource_handles.count(uri) != 0)
        {
            return getResource<T>(m_resource_handles[uri]);
        }

        const std::type_info& type = typeid(T);
        if (m_resource_loaders.count(type.name()) == 0)
        {
            LOG_ERROR("cannot find a valid loadr for: {}", type.name());
            return {};
        }

        std::shared_ptr<Loader<T, CI>> loader = std::static_pointer_cast<Loader<T, CI>>(m_resource_loaders[type.name()]);
        std::shared_ptr<T>             res    = loader.createResource(uri);
        if (res != nullptr)
        {
            auto handle                    = createHandle<T>();
            m_resource_handles[uri]        = res;
            m_resource_handles_inv[handle] = res;
            addResource<T>(handle, res);
            return res;
        }
        return {};
    }

    template<typename T, typename CI>
    std::weak_ptr<T> ResourceManager::loadResource(const std::string& uri, const CI& create_info)
    {
        if (m_resource_handles.count(uri) != 0)
        {
            return getResource<T>(m_resource_handles[uri]);
        }

        const std::type_info& type = typeid(T);
        if (m_resource_loaders.count(type.name()) == 0)
        {
            LOG_ERROR("cannot find a valid loadr for: {}", type.name());
            return {};
        }

        std::shared_ptr<Loader<T, CI>> loader = std::static_pointer_cast<Loader<T, CI>>(m_resource_loaders[type.name()]);
        std::shared_ptr<T>             res    = loader.createResource(create_info);
        if (res != nullptr)
        {
            auto handle                    = createHandle<T>();
            m_resource_handles[uri]        = res;
            m_resource_handles_inv[handle] = res;
            addResource<T>(handle, res);
            return res;
        }

        return {};
    }

} // namespace ArchViz