LCOV - code coverage report
Current view: top level - include/openmodelviewer/core/resource - resource_manager.hpp (source / functions) Hit Total Coverage
Test: libopenmodelviewer coverage Lines: 42 43 97.7 %
Date: 2025-07-03 20:26:48 Functions: 11 11 100.0 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.14