Line data Source code
1 : // 2 : // Copyright 2024 OpenModelViewer Authors 3 : // 4 : // Licensed under the Apache License, Version 2.0 (the "License"); 5 : // you may not use this file except in compliance with the License. 6 : // You may obtain a copy of the License at 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : // 15 : 16 : #pragma once 17 : 18 : #include "openmodelviewer/core/async/task_scheduler.hpp" 19 : #include "openmodelviewer/core/async/task_handle.hpp" 20 : #include "openmodelviewer/core/async/itask_data.hpp" 21 : 22 : #include "openmodelviewer/core/resource/resource_handle_generator.hpp" 23 : #include "openmodelviewer/core/resource/resource_handle.hpp" 24 : #include "openmodelviewer/core/resource/resource_loader_registry.hpp" 25 : #include "openmodelviewer/core/resource/resource_cache.hpp" 26 : #include "openmodelviewer/core/resource/resource_status.hpp" 27 : 28 : #include <filesystem> 29 : #include <mutex> 30 : #include <shared_mutex> 31 : #include <unordered_map> 32 : #include <memory> 33 : #include <typeindex> 34 : #include <stdexcept> 35 : #include <exception> 36 : 37 : namespace openmodelviewer::core::resource 38 : { 39 : using namespace async; 40 : 41 : /** 42 : * @brief Central manager for asynchronous resource loading and access. 43 : * 44 : * The ResourceManager coordinates loading operations through a TaskScheduler 45 : * and registered type-specific loaders. It assigns unique handles to each resource 46 : * and stores them in type-safe caches for fast retrieval and cleanup. 47 : * 48 : * @note When the ResourceManager is destroyed, all internal caches and pending tasks are discarded. 49 : * Resources returned as std::shared_ptr<T> remain valid as long as they are still held externally. 50 : */ 51 : class ResourceManager : public std::enable_shared_from_this<ResourceManager> 52 : { 53 : public: 54 : /** 55 : * @brief Constructs a ResourceManager using a shared TaskScheduler. 56 : * @param scheduler The global task scheduler used to schedule loading operations. 57 : */ 58 : ResourceManager(TaskScheduler& scheduler); 59 : 60 : /** 61 : * @brief Destructor, clears all caches and pending tasks before destruction. 62 : */ 63 : ~ResourceManager(); 64 : 65 : /** 66 : * @brief Returns the current status of a resource identified by its handle. 67 : * 68 : * @param handle The resource handle whose status to query. 69 : * @return The current ResourceStatus of the seeked resource. 70 : */ 71 : ResourceStatus getStatus(const ResourceHandle& handle) const noexcept; 72 : 73 : /** 74 : * @brief Rethrows the exception associated with a failed resource and frees it's data from the cache. 75 : * 76 : * @param handle The handle of the failed resource. 77 : * @throws std::logic_error If the task did not fail. 78 : * @throws std::runtime_error If the handle is not associated with any cached resource. 79 : * @throws The original exception that caused the resource loading to fail. 80 : * 81 : * @note This method can only be called if the resource previously failed, otherwise std::logic_error will be thrown. 82 : */ 83 : void rethrowAndForget(const ResourceHandle& handle); 84 : 85 : /** 86 : * @brief Retrieves the exception associated with a failed resource and frees it's data from the cache. 87 : * 88 : * If the specified resource handle corresponds to a failed loading task, this function 89 : * returns its stored exception pointer and frees it's data from the cache. 90 : * 91 : * @param handle The handle of the failed resource. 92 : * @return A std::exception_ptr representing the failure, or nullptr if unavailable. 93 : * 94 : * @note This method can only be called if the resource previously failed, otherwise nullptr will be return. 95 : */ 96 : std::exception_ptr getErrorAndForget(const ResourceHandle& handle) noexcept; 97 : 98 : /** 99 : * @brief Frees a resource and its meta data. 100 : * 101 : * This function removes the resource from its associated ResourceCache 102 : * and all it's allocated meta data. 103 : * If the resource is still loading it is not removed. 104 : * 105 : * @param handle The handle identifying the resource to free. 106 : * @return True if the resource was successfully freed. 107 : */ 108 : bool free(const ResourceHandle& handle) noexcept; 109 : 110 : /** 111 : * @brief Clears all loaded and pending resources from the manager. 112 : * 113 : * This function removes: 114 : * - all pending tasks still in progress or failed, 115 : * - all cached resources from all typed resource caches. 116 : * 117 : * Already returned shared_ptr resources will remain valid as long as they are still referenced. 118 : */ 119 : void clear() noexcept; 120 : 121 : /** 122 : * @brief Load a resource asynchronously and track it by handle. 123 : * 124 : * This method schedules a loading task using the registered loader for the specified type. 125 : * Upon success, the resource is stored internally and can be accessed and managed by handle. 126 : * 127 : * @tparam RType The type of resource to load. Must have a registered loader in ResourceLoaderRegistry<RType>. 128 : * @param resourcePath The path to the resource file. 129 : * @return A ResourceHandle uniquely identifying the loading task and eventual resource. 130 : * @throws std::runtime_error If no loader is registered at runtime, or if handle collision occurs. 131 : * @throws std::logic_error (indirectly) if callback configuration fails via TaskScheduler. 132 : */ 133 : template <typename RType> 134 27 : inline ResourceHandle load(const std::filesystem::path& resourcePath) 135 : { 136 27 : const auto& loader = ResourceLoaderRegistry<RType>::loader; 137 27 : if (!loader) 138 : { 139 1 : throw std::runtime_error("No loader registered for this resource type."); 140 : } 141 : 142 26 : ResourceHandle rHandle = m_handleGenerator.generate(); 143 : 144 26 : TaskHandle tHandle = m_scheduler.schedule( 145 26 : [resourcePath, loader] () -> RType 146 : { 147 26 : return loader(resourcePath); 148 : } 149 : ); 150 : 151 26 : auto taskData = m_scheduler.getTaskData(tHandle); 152 : 153 26 : std::weak_ptr<ResourceManager> weakThis = shared_from_this(); 154 26 : m_scheduler.setCallback<RType>( 155 : tHandle, 156 26 : [weakThis, rHandle](const TaskResult<RType>& result) -> void 157 : { 158 26 : auto self = weakThis.lock(); 159 26 : if (self && !result.errored()) 160 : { 161 19 : auto sptr = std::make_shared<RType>(result.data.value()); 162 : 163 : { 164 19 : std::lock_guard<std::mutex> lockg(self->m_loadMutex); 165 19 : self->m_loadTasks.erase(rHandle); 166 19 : } 167 : 168 19 : self->store(rHandle, std::move(sptr)); 169 19 : } 170 26 : } 171 : ); 172 : 173 26 : std::lock_guard<std::mutex> lockg(m_loadMutex); 174 26 : auto [it, inserted] = m_loadTasks.emplace(rHandle, std::move(taskData)); 175 26 : if (!inserted) 176 : { 177 0 : throw std::runtime_error("Collision detected while generating ResourceHandle."); 178 : } 179 : 180 26 : return rHandle; 181 26 : } 182 : 183 : /** 184 : * @brief Retrieves a previously loaded resource of the specified type. 185 : * 186 : * @tparam RType The type of the resource to retrieve. 187 : * @param handle The handle used to identify the resource. 188 : * @return A shared pointer to the resource, or nullptr if not found. 189 : */ 190 : template <typename RType> 191 14 : inline std::shared_ptr<RType> get(const ResourceHandle& handle) const 192 : { 193 14 : std::shared_lock<std::shared_mutex> ulock(m_cacheMutex); 194 14 : auto it = m_cache.find(typeid(RType)); 195 14 : if (it == m_cache.end()) 196 : { 197 4 : return nullptr; 198 : } 199 : 200 10 : auto* typed = static_cast<ResourceCache<RType>*>(it->second.get()); 201 10 : return typed->get(handle); 202 14 : } 203 : 204 : private: 205 : TaskScheduler& m_scheduler; 206 : ResourceHandleGenerator m_handleGenerator; 207 : 208 : mutable std::mutex m_loadMutex; 209 : mutable std::shared_mutex m_cacheMutex; 210 : std::unordered_map<ResourceHandle, std::shared_ptr<ITaskData>> m_loadTasks; 211 : std::unordered_map<std::type_index, std::unique_ptr<IResourceCache>> m_cache; 212 : 213 : bool isCached(const ResourceHandle& handle) const noexcept; 214 : 215 : template <typename RType> 216 19 : inline void store(ResourceHandle handle, std::shared_ptr<RType> resource) 217 : { 218 19 : std::unique_lock<std::shared_mutex> ulock(m_cacheMutex); 219 19 : std::unique_ptr<IResourceCache>& ptr = m_cache[typeid(RType)]; 220 : 221 19 : if (!ptr) 222 : { 223 17 : ptr = std::make_unique<ResourceCache<RType>>(); 224 : } 225 : 226 19 : auto* typed = static_cast<ResourceCache<RType>*>(ptr.get()); 227 19 : typed->insert(handle, std::move(resource)); 228 19 : } 229 : }; 230 : } // namespace openmodelviewer::core::resource