Extra2D/include/assets/asset_storage.h

336 lines
9.8 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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