2026-03-02 22:44:42 +08:00
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
|
|
#include <assets/handle.h>
|
|
|
|
|
|
#include <types/ptr/intrusive_ptr.h>
|
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
#include <cstdint>
|
2026-03-03 19:32:23 +08:00
|
|
|
|
#include <chrono>
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
#include <unordered_map>
|
2026-03-02 22:44:42 +08:00
|
|
|
|
|
|
|
|
|
|
namespace extra2d {
|
|
|
|
|
|
|
2026-03-03 19:32:23 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 资源加载状态
|
|
|
|
|
|
*/
|
|
|
|
|
|
enum class AssetLoadState {
|
|
|
|
|
|
Unloaded, ///< 未加载
|
|
|
|
|
|
Loading, ///< 加载中
|
|
|
|
|
|
Loaded, ///< 已加载
|
|
|
|
|
|
Failed ///< 加载失败
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-02 22:44:42 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 密集存储的资源仓库
|
|
|
|
|
|
*
|
|
|
|
|
|
* 使用 Dense Vec 存储,类似 ECS 的组件存储。
|
|
|
|
|
|
* 提供 O(1) 的插入、删除、访问。
|
2026-03-03 19:32:23 +08:00
|
|
|
|
* 支持引用统计和 LRU(最近最少使用)缓存策略。
|
2026-03-02 22:44:42 +08:00
|
|
|
|
*
|
|
|
|
|
|
* @tparam T 资源类型
|
|
|
|
|
|
*/
|
|
|
|
|
|
template<typename T>
|
|
|
|
|
|
class AssetStorage {
|
|
|
|
|
|
public:
|
2026-03-03 19:32:23 +08:00
|
|
|
|
using TimePoint = std::chrono::steady_clock::time_point;
|
|
|
|
|
|
|
2026-03-02 22:44:42 +08:00
|
|
|
|
struct Slot {
|
|
|
|
|
|
Ptr<T> asset;
|
|
|
|
|
|
typename Handle<T>::Generation generation = 0;
|
|
|
|
|
|
bool active = false;
|
2026-03-03 19:32:23 +08:00
|
|
|
|
AssetLoadState loadState = AssetLoadState::Unloaded;
|
|
|
|
|
|
TimePoint lastAccessTime; ///< 最后访问时间(用于 LRU)
|
2026-03-02 22:44:42 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
2026-03-03 19:32:23 +08:00
|
|
|
|
auto now = std::chrono::steady_clock::now();
|
2026-03-02 22:44:42 +08:00
|
|
|
|
|
|
|
|
|
|
if (!freeIndices_.empty()) {
|
|
|
|
|
|
index = freeIndices_.back();
|
|
|
|
|
|
freeIndices_.pop_back();
|
|
|
|
|
|
slots_[index].asset = std::move(asset);
|
|
|
|
|
|
slots_[index].active = true;
|
2026-03-03 19:32:23 +08:00
|
|
|
|
slots_[index].loadState = AssetLoadState::Loaded;
|
|
|
|
|
|
slots_[index].lastAccessTime = now;
|
2026-03-02 22:44:42 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
index = static_cast<uint32_t>(slots_.size());
|
|
|
|
|
|
Slot slot;
|
|
|
|
|
|
slot.asset = std::move(asset);
|
|
|
|
|
|
slot.generation = nextGeneration_++;
|
|
|
|
|
|
slot.active = true;
|
2026-03-03 19:32:23 +08:00
|
|
|
|
slot.loadState = AssetLoadState::Loaded;
|
|
|
|
|
|
slot.lastAccessTime = now;
|
2026-03-02 22:44:42 +08:00
|
|
|
|
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 资源指针
|
|
|
|
|
|
*/
|
2026-03-03 19:32:23 +08:00
|
|
|
|
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 {
|
2026-03-02 22:44:42 +08:00
|
|
|
|
if (!isValid(handle)) return nullptr;
|
|
|
|
|
|
return slots_[handle.index()].asset.get();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief 获取资源(返回智能指针)
|
|
|
|
|
|
* @param handle 资源句柄
|
|
|
|
|
|
* @return 资源智能指针
|
|
|
|
|
|
*/
|
2026-03-03 19:32:23 +08:00
|
|
|
|
Ptr<T> getPtr(Handle<T> handle) {
|
|
|
|
|
|
if (!isValid(handle)) return Ptr<T>();
|
|
|
|
|
|
updateAccessTime(handle);
|
|
|
|
|
|
return slots_[handle.index()].asset;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief 获取资源(返回智能指针) - 常量版本,不更新访问时间
|
|
|
|
|
|
* @param handle 资源句柄
|
|
|
|
|
|
* @return 资源智能指针
|
|
|
|
|
|
*/
|
2026-03-02 22:44:42 +08:00
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 19:32:23 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 22:44:42 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @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(); }
|
|
|
|
|
|
|
2026-03-03 19:32:23 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 22:44:42 +08:00
|
|
|
|
private:
|
|
|
|
|
|
std::vector<Slot> slots_;
|
|
|
|
|
|
std::vector<uint32_t> freeIndices_;
|
|
|
|
|
|
uint32_t nextGeneration_ = 1;
|
|
|
|
|
|
size_t activeCount_ = 0;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace extra2d
|