#pragma once #include #include #include #include #include #include #include namespace extra2d { /** * @brief 资源加载状态 */ enum class AssetLoadState { Unloaded, ///< 未加载 Loading, ///< 加载中 Loaded, ///< 已加载 Failed ///< 加载失败 }; /** * @brief 密集存储的资源仓库 * * 使用 Dense Vec 存储,类似 ECS 的组件存储。 * 提供 O(1) 的插入、删除、访问。 * 支持引用统计和 LRU(最近最少使用)缓存策略。 * * @tparam T 资源类型 */ template class AssetStorage { public: using TimePoint = std::chrono::steady_clock::time_point; struct Slot { Ptr asset; typename Handle::Generation generation = 0; bool active = false; AssetLoadState loadState = AssetLoadState::Unloaded; TimePoint lastAccessTime; ///< 最后访问时间(用于 LRU) }; AssetStorage() = default; ~AssetStorage() = default; AssetStorage(const AssetStorage&) = delete; AssetStorage& operator=(const AssetStorage&) = delete; /** * @brief 插入资源,返回句柄 * @param asset 资源指针 * @return 资源句柄 */ Handle insert(Ptr asset) { uint32_t index; auto now = std::chrono::steady_clock::now(); if (!freeIndices_.empty()) { index = freeIndices_.back(); freeIndices_.pop_back(); slots_[index].asset = std::move(asset); slots_[index].active = true; slots_[index].loadState = AssetLoadState::Loaded; slots_[index].lastAccessTime = now; } else { index = static_cast(slots_.size()); Slot slot; slot.asset = std::move(asset); slot.generation = nextGeneration_++; slot.active = true; slot.loadState = AssetLoadState::Loaded; slot.lastAccessTime = now; slots_.push_back(std::move(slot)); } ++activeCount_; return Handle(index, slots_[index].generation); } /** * @brief 移除资源 * @param handle 资源句柄 */ void remove(Handle handle) { if (!isValid(handle)) return; uint32_t index = handle.index(); slots_[index].asset.reset(); slots_[index].active = false; slots_[index].generation = nextGeneration_++; freeIndices_.push_back(index); --activeCount_; } /** * @brief 获取资源(返回指针,可能为 nullptr) * @param handle 资源句柄 * @return 资源指针 */ T* get(Handle handle) { if (!isValid(handle)) return nullptr; updateAccessTime(handle); return slots_[handle.index()].asset.get(); } /** * @brief 获取资源(返回指针,可能为 nullptr) - 常量版本,不更新访问时间 * @param handle 资源句柄 * @return 资源指针 */ const T* get(Handle handle) const { if (!isValid(handle)) return nullptr; return slots_[handle.index()].asset.get(); } /** * @brief 获取资源(返回智能指针) * @param handle 资源句柄 * @return 资源智能指针 */ Ptr getPtr(Handle handle) { if (!isValid(handle)) return Ptr(); updateAccessTime(handle); return slots_[handle.index()].asset; } /** * @brief 获取资源(返回智能指针) - 常量版本,不更新访问时间 * @param handle 资源句柄 * @return 资源智能指针 */ Ptr getPtr(Handle handle) const { if (!isValid(handle)) return Ptr(); return slots_[handle.index()].asset; } /** * @brief 检查句柄是否有效 * @param handle 资源句柄 * @return 是否有效 */ bool isValid(Handle handle) const { if (!handle.isValid()) return false; uint32_t index = handle.index(); if (index >= slots_.size()) return false; const Slot& slot = slots_[index]; return slot.active && slot.generation == handle.generation(); } /** * @brief 获取资源加载状态 * @param handle 资源句柄 * @return 加载状态 */ AssetLoadState getLoadState(Handle handle) const { if (!isValid(handle)) return AssetLoadState::Unloaded; return slots_[handle.index()].loadState; } /** * @brief 设置资源加载状态 * @param handle 资源句柄 * @param state 加载状态 */ void setLoadState(Handle handle, AssetLoadState state) { if (!isValid(handle)) return; slots_[handle.index()].loadState = state; } /** * @brief 检查资源是否正在加载 * @param handle 资源句柄 * @return 是否正在加载 */ bool isLoading(Handle handle) const { return getLoadState(handle) == AssetLoadState::Loading; } /** * @brief 检查资源是否已加载 * @param handle 资源句柄 * @return 是否已加载 */ bool isLoaded(Handle handle) const { return getLoadState(handle) == AssetLoadState::Loaded; } /** * @brief 遍历所有资源 * @param func 回调函数,签名 void(Handle, T*) */ template void forEach(Func&& func) const { for (size_t i = 0; i < slots_.size(); ++i) { const Slot& slot = slots_[i]; if (slot.active && slot.asset) { func(Handle(static_cast(i), slot.generation), slot.asset.get()); } } } /** * @brief 清空所有资源 */ void clear() { slots_.clear(); freeIndices_.clear(); activeCount_ = 0; } /** * @brief 获取活跃资源数量 */ size_t count() const { return activeCount_; } /** * @brief 获取存储容量 */ size_t capacity() const { return slots_.size(); } /** * @brief 获取资源引用计数 * @param handle 资源句柄 * @return 引用计数 */ uint32_t getRefCount(Handle handle) const { if (!isValid(handle)) return 0; const auto& slot = slots_[handle.index()]; return slot.asset ? slot.asset.refCount() : 0; } /** * @brief 获取资源最后访问时间 * @param handle 资源句柄 * @return 最后访问时间 */ TimePoint getLastAccessTime(Handle handle) const { if (!isValid(handle)) return TimePoint(); return slots_[handle.index()].lastAccessTime; } /** * @brief 更新资源访问时间 * @param handle 资源句柄 */ void updateAccessTime(Handle handle) { if (!isValid(handle)) return; slots_[handle.index()].lastAccessTime = std::chrono::steady_clock::now(); } /** * @brief 查找可以卸载的资源(引用计数为1的资源,只有AssetStorage自己持有引用) * @param maxAgeSeconds 最大空闲时间(秒),默认300秒 * @return 可以卸载的资源句柄列表 */ std::vector> findUnloadableResources(int maxAgeSeconds = 300) const { std::vector> result; auto now = std::chrono::steady_clock::now(); for (size_t i = 0; i < slots_.size(); ++i) { const Slot& slot = slots_[i]; if (slot.active && slot.asset) { // 引用计数为1表示只有 AssetStorage 自己持有引用 if (slot.asset.refCount() == 1) { auto age = std::chrono::duration_cast( now - slot.lastAccessTime).count(); if (age >= maxAgeSeconds) { result.emplace_back(static_cast(i), slot.generation); } } } } // 按访问时间排序,最久未使用的排在前面 std::sort(result.begin(), result.end(), [this](const Handle& a, const Handle& b) { return slots_[a.index()].lastAccessTime < slots_[b.index()].lastAccessTime; }); return result; } /** * @brief 卸载指定资源(只有引用计数为1时才会真正卸载) * @param handle 资源句柄 * @return 是否成功卸载 */ bool unloadResource(Handle handle) { if (!isValid(handle)) return false; auto& slot = slots_[handle.index()]; if (!slot.asset) return false; // 只有引用计数为1时才可以卸载(只有AssetStorage自己持有) if (slot.asset.refCount() > 1) { return false; } slot.asset.reset(); slot.loadState = AssetLoadState::Unloaded; return true; } /** * @brief 批量卸载可卸载的资源 * @param maxCount 最大卸载数量,-1表示全部卸载 * @param maxAgeSeconds 最大空闲时间(秒) * @return 实际卸载的资源数量 */ size_t unloadOldResources(int maxCount = -1, int maxAgeSeconds = 300) { auto unloadable = findUnloadableResources(maxAgeSeconds); size_t unloaded = 0; for (const auto& handle : unloadable) { if (maxCount >= 0 && unloaded >= static_cast(maxCount)) { break; } if (unloadResource(handle)) { ++unloaded; } } return unloaded; } private: std::vector slots_; std::vector freeIndices_; uint32_t nextGeneration_ = 1; size_t activeCount_ = 0; }; } // namespace extra2d