Extra2D/include/assets/asset_storage.h

336 lines
9.8 KiB
C
Raw Normal View History

#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 1AssetStorage自己持有引用
* @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