feat(assets): 添加资源热重载和智能卸载功能
实现纹理和着色器的热重载功能,支持文件修改后自动重新加载 添加资源引用计数和LRU缓存机制,支持智能卸载长时间未使用的资源 重构文件监控系统,减少重复代码 扩展AssetStorage功能,增加加载状态管理和访问时间追踪
This commit is contained in:
parent
e0b0a7883d
commit
44e0d65f10
|
|
@ -4,24 +4,42 @@
|
|||
#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;
|
||||
|
|
@ -37,18 +55,23 @@ public:
|
|||
*/
|
||||
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));
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +99,18 @@ public:
|
|||
* @param handle 资源句柄
|
||||
* @return 资源指针
|
||||
*/
|
||||
T* get(Handle<T> handle) const {
|
||||
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();
|
||||
}
|
||||
|
|
@ -86,6 +120,17 @@ public:
|
|||
* @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;
|
||||
|
|
@ -104,6 +149,44 @@ public:
|
|||
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*)
|
||||
|
|
@ -137,6 +220,111 @@ public:
|
|||
*/
|
||||
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_;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <assets/asset_loader.h>
|
||||
#include <assets/asset_storage.h>
|
||||
#include <assets/asset_loader.h>
|
||||
#include <assets/handle.h>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
|
|
@ -103,11 +103,52 @@ public:
|
|||
*/
|
||||
template <typename T> bool isValid(Handle<T> handle) const;
|
||||
|
||||
/**
|
||||
* @brief 检查资源是否正在加载
|
||||
*/
|
||||
template <typename T> bool isLoading(Handle<T> handle) const;
|
||||
|
||||
/**
|
||||
* @brief 检查资源是否已加载
|
||||
*/
|
||||
template <typename T> bool isLoaded(Handle<T> handle) const;
|
||||
|
||||
/**
|
||||
* @brief 获取资源加载状态
|
||||
*/
|
||||
template <typename T> AssetLoadState getLoadState(Handle<T> handle) const;
|
||||
|
||||
/**
|
||||
* @brief 移除资源
|
||||
*/
|
||||
template <typename T> void remove(Handle<T> handle);
|
||||
|
||||
//===========================================================================
|
||||
// 资源引用统计与智能卸载
|
||||
//===========================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取资源引用计数
|
||||
*/
|
||||
template <typename T> uint32_t getRefCount(Handle<T> handle) const;
|
||||
|
||||
/**
|
||||
* @brief 查找可以卸载的资源
|
||||
*/
|
||||
template <typename T>
|
||||
std::vector<Handle<T>> findUnloadableResources(int maxAgeSeconds = 300) const;
|
||||
|
||||
/**
|
||||
* @brief 卸载指定资源
|
||||
*/
|
||||
template <typename T> bool unloadResource(Handle<T> handle);
|
||||
|
||||
/**
|
||||
* @brief 批量卸载可卸载的资源
|
||||
*/
|
||||
template <typename T>
|
||||
size_t unloadOldResources(int maxCount = -1, int maxAgeSeconds = 300);
|
||||
|
||||
//===========================================================================
|
||||
// 批量加载(目录/包)
|
||||
//===========================================================================
|
||||
|
|
@ -215,6 +256,12 @@ private:
|
|||
void addFileWatch(const std::string &path, Handle<Texture> handle);
|
||||
void addFileWatch(const std::string &path, Handle<Shader> handle);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 内部实现:添加文件监控(减少重复代码)
|
||||
*/
|
||||
void addFileWatchInternal(const std::string &path, FileWatchInfo &&info);
|
||||
|
||||
/**
|
||||
* @brief 重新加载纹理
|
||||
*/
|
||||
|
|
@ -470,6 +517,75 @@ template <> inline bool AssetsModule::isValid<Mesh>(Handle<Mesh> handle) const {
|
|||
return meshes_.isValid(handle);
|
||||
}
|
||||
|
||||
template <typename T> bool AssetsModule::isLoading(Handle<T> handle) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool AssetsModule::isLoading<Texture>(Handle<Texture> handle) const {
|
||||
return textures_.isLoading(handle);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool AssetsModule::isLoading<Shader>(Handle<Shader> handle) const {
|
||||
return shaders_.isLoading(handle);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool AssetsModule::isLoading<Material>(Handle<Material> handle) const {
|
||||
return materials_.isLoading(handle);
|
||||
}
|
||||
|
||||
template <> inline bool AssetsModule::isLoading<Mesh>(Handle<Mesh> handle) const {
|
||||
return meshes_.isLoading(handle);
|
||||
}
|
||||
|
||||
template <typename T> bool AssetsModule::isLoaded(Handle<T> handle) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool AssetsModule::isLoaded<Texture>(Handle<Texture> handle) const {
|
||||
return textures_.isLoaded(handle);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool AssetsModule::isLoaded<Shader>(Handle<Shader> handle) const {
|
||||
return shaders_.isLoaded(handle);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool AssetsModule::isLoaded<Material>(Handle<Material> handle) const {
|
||||
return materials_.isLoaded(handle);
|
||||
}
|
||||
|
||||
template <> inline bool AssetsModule::isLoaded<Mesh>(Handle<Mesh> handle) const {
|
||||
return meshes_.isLoaded(handle);
|
||||
}
|
||||
|
||||
template <typename T> AssetLoadState AssetsModule::getLoadState(Handle<T> handle) const {
|
||||
return AssetLoadState::Unloaded;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline AssetLoadState AssetsModule::getLoadState<Texture>(Handle<Texture> handle) const {
|
||||
return textures_.getLoadState(handle);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline AssetLoadState AssetsModule::getLoadState<Shader>(Handle<Shader> handle) const {
|
||||
return shaders_.getLoadState(handle);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline AssetLoadState AssetsModule::getLoadState<Material>(Handle<Material> handle) const {
|
||||
return materials_.getLoadState(handle);
|
||||
}
|
||||
|
||||
template <> inline AssetLoadState AssetsModule::getLoadState<Mesh>(Handle<Mesh> handle) const {
|
||||
return meshes_.getLoadState(handle);
|
||||
}
|
||||
|
||||
template <typename T> void AssetsModule::remove(Handle<T> handle) {}
|
||||
|
||||
template <> inline void AssetsModule::remove<Texture>(Handle<Texture> handle) {
|
||||
|
|
@ -489,6 +605,108 @@ template <> inline void AssetsModule::remove<Mesh>(Handle<Mesh> handle) {
|
|||
meshes_.remove(handle);
|
||||
}
|
||||
|
||||
template <typename T> uint32_t AssetsModule::getRefCount(Handle<T> handle) const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline uint32_t AssetsModule::getRefCount<Texture>(Handle<Texture> handle) const {
|
||||
return textures_.getRefCount(handle);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline uint32_t AssetsModule::getRefCount<Shader>(Handle<Shader> handle) const {
|
||||
return shaders_.getRefCount(handle);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline uint32_t AssetsModule::getRefCount<Material>(Handle<Material> handle) const {
|
||||
return materials_.getRefCount(handle);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline uint32_t AssetsModule::getRefCount<Mesh>(Handle<Mesh> handle) const {
|
||||
return meshes_.getRefCount(handle);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<Handle<T>> AssetsModule::findUnloadableResources(int maxAgeSeconds) const {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::vector<Handle<Texture>>
|
||||
AssetsModule::findUnloadableResources<Texture>(int maxAgeSeconds) const {
|
||||
return textures_.findUnloadableResources(maxAgeSeconds);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::vector<Handle<Shader>>
|
||||
AssetsModule::findUnloadableResources<Shader>(int maxAgeSeconds) const {
|
||||
return shaders_.findUnloadableResources(maxAgeSeconds);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::vector<Handle<Material>>
|
||||
AssetsModule::findUnloadableResources<Material>(int maxAgeSeconds) const {
|
||||
return materials_.findUnloadableResources(maxAgeSeconds);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::vector<Handle<Mesh>>
|
||||
AssetsModule::findUnloadableResources<Mesh>(int maxAgeSeconds) const {
|
||||
return meshes_.findUnloadableResources(maxAgeSeconds);
|
||||
}
|
||||
|
||||
template <typename T> bool AssetsModule::unloadResource(Handle<T> handle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool AssetsModule::unloadResource<Texture>(Handle<Texture> handle) {
|
||||
return textures_.unloadResource(handle);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool AssetsModule::unloadResource<Shader>(Handle<Shader> handle) {
|
||||
return shaders_.unloadResource(handle);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool AssetsModule::unloadResource<Material>(Handle<Material> handle) {
|
||||
return materials_.unloadResource(handle);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool AssetsModule::unloadResource<Mesh>(Handle<Mesh> handle) {
|
||||
return meshes_.unloadResource(handle);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
size_t AssetsModule::unloadOldResources(int maxCount, int maxAgeSeconds) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline size_t AssetsModule::unloadOldResources<Texture>(int maxCount, int maxAgeSeconds) {
|
||||
return textures_.unloadOldResources(maxCount, maxAgeSeconds);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline size_t AssetsModule::unloadOldResources<Shader>(int maxCount, int maxAgeSeconds) {
|
||||
return shaders_.unloadOldResources(maxCount, maxAgeSeconds);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline size_t AssetsModule::unloadOldResources<Material>(int maxCount, int maxAgeSeconds) {
|
||||
return materials_.unloadOldResources(maxCount, maxAgeSeconds);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline size_t AssetsModule::unloadOldResources<Mesh>(int maxCount, int maxAgeSeconds) {
|
||||
return meshes_.unloadOldResources(maxCount, maxAgeSeconds);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<Handle<T>> AssetsModule::loadDir(const std::string &directory,
|
||||
const std::string &pattern,
|
||||
|
|
@ -509,6 +727,10 @@ template <>
|
|||
void AssetsModule::loadAsync<Texture>(
|
||||
const std::string &path, std::function<void(Handle<Texture>)> callback);
|
||||
|
||||
template <>
|
||||
void AssetsModule::loadAsync<Shader>(
|
||||
const std::string &path, std::function<void(Handle<Shader>)> callback);
|
||||
|
||||
template <typename T>
|
||||
void AssetsModule::registerLoader(std::unique_ptr<AssetLoader<T>> loader) {}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,6 +78,25 @@ public:
|
|||
*/
|
||||
uint32_t getUniformBlockBinding(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief 从源码重新加载着色器
|
||||
* @param vsSource 顶点着色器源码
|
||||
* @param fsSource 片段着色器源码
|
||||
* @return 重新加载是否成功
|
||||
*/
|
||||
bool reloadFromSource(const std::string &vsSource, const std::string &fsSource);
|
||||
|
||||
/**
|
||||
* @brief 使用自定义顶点布局从源码重新加载着色器
|
||||
* @param vsSource 顶点着色器源码
|
||||
* @param fsSource 片段着色器源码
|
||||
* @param vertexLayout 顶点布局
|
||||
* @return 重新加载是否成功
|
||||
*/
|
||||
bool reloadFromSourceWithLayout(const std::string &vsSource,
|
||||
const std::string &fsSource,
|
||||
const VertexLayout &vertexLayout);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 添加版本声明(如果不存在)
|
||||
|
|
|
|||
|
|
@ -82,6 +82,23 @@ public:
|
|||
*/
|
||||
bool isLoaded() const { return handle_.isValid(); }
|
||||
|
||||
/**
|
||||
* @brief 从文件重新加载纹理
|
||||
* @param path 文件路径
|
||||
* @return 重新加载是否成功
|
||||
*/
|
||||
bool reloadFromFile(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 从内存重新加载纹理
|
||||
* @param data 像素数据
|
||||
* @param width 宽度
|
||||
* @param height 高度
|
||||
* @param format 像素格式
|
||||
* @return 重新加载是否成功
|
||||
*/
|
||||
bool reloadFromMemory(const uint8_t* data, int width, int height, TextureFormat format);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 获取每个像素的字节数
|
||||
|
|
|
|||
|
|
@ -396,12 +396,38 @@ AssetsModule::loadDir<Texture>(const std::string &directory,
|
|||
template <>
|
||||
void AssetsModule::loadAsync<Texture>(
|
||||
const std::string &path, std::function<void(Handle<Texture>)> callback) {
|
||||
std::thread([this, path, callback]() {
|
||||
Handle<Texture> handle = load<Texture>(path);
|
||||
if (callback) {
|
||||
callback(handle);
|
||||
// 确保异步加载系统已初始化
|
||||
if (!asyncLoaderRunning_) {
|
||||
initAsyncLoader();
|
||||
}
|
||||
}).detach();
|
||||
|
||||
// 创建并提交加载任务
|
||||
LoadTask task;
|
||||
task.type = LoadTask::Type::Texture;
|
||||
task.priority = LoadTask::Priority::Normal;
|
||||
task.path = path;
|
||||
task.textureCallback = callback;
|
||||
|
||||
submitLoadTask(task);
|
||||
}
|
||||
|
||||
template <>
|
||||
void AssetsModule::loadAsync<Shader>(
|
||||
const std::string &path, std::function<void(Handle<Shader>)> callback) {
|
||||
// 确保异步加载系统已初始化
|
||||
if (!asyncLoaderRunning_) {
|
||||
initAsyncLoader();
|
||||
}
|
||||
|
||||
// 创建并提交加载任务
|
||||
LoadTask task;
|
||||
task.type = LoadTask::Type::Shader;
|
||||
task.priority = LoadTask::Priority::Normal;
|
||||
task.path = path;
|
||||
task.secondaryPath = "";
|
||||
task.shaderCallback = callback;
|
||||
|
||||
submitLoadTask(task);
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
|
@ -568,46 +594,36 @@ void AssetsModule::setHotReloadInterval(float interval) {
|
|||
hotReloadInterval_ = interval;
|
||||
}
|
||||
|
||||
void AssetsModule::addFileWatch(const std::string &path,
|
||||
Handle<Texture> handle) {
|
||||
void AssetsModule::addFileWatchInternal(const std::string &path, FileWatchInfo &&info) {
|
||||
try {
|
||||
if (!fileExists(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
FileWatchInfo info;
|
||||
info.path = path;
|
||||
info.lastWriteTime = getLastWriteTime(path);
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
fileWatchList_.push_back(std::move(info));
|
||||
const char *type = fileWatchList_.back().isTexture ? "texture" : "shader";
|
||||
E2D_LOG_DEBUG("Watching {} file: {}", type, path);
|
||||
} catch (const std::exception &e) {
|
||||
E2D_LOG_ERROR("Failed to add file watch for {}: {}", path, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void AssetsModule::addFileWatch(const std::string &path, Handle<Texture> handle) {
|
||||
FileWatchInfo info;
|
||||
info.textureHandle = handle;
|
||||
info.isTexture = true;
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
fileWatchList_.push_back(info);
|
||||
E2D_LOG_DEBUG("Watching texture file: {}", path);
|
||||
} catch (const std::exception &e) {
|
||||
E2D_LOG_ERROR("Failed to add file watch for {}: {}", path, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void AssetsModule::addFileWatch(const std::string &path,
|
||||
Handle<Shader> handle) {
|
||||
try {
|
||||
if (!fileExists(path)) {
|
||||
return;
|
||||
addFileWatchInternal(path, std::move(info));
|
||||
}
|
||||
|
||||
void AssetsModule::addFileWatch(const std::string &path, Handle<Shader> handle) {
|
||||
FileWatchInfo info;
|
||||
info.path = path;
|
||||
info.lastWriteTime = getLastWriteTime(path);
|
||||
info.shaderHandle = handle;
|
||||
info.isTexture = false;
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
fileWatchList_.push_back(info);
|
||||
E2D_LOG_DEBUG("Watching shader file: {}", path);
|
||||
} catch (const std::exception &e) {
|
||||
E2D_LOG_ERROR("Failed to add file watch for {}: {}", path, e.what());
|
||||
}
|
||||
addFileWatchInternal(path, std::move(info));
|
||||
}
|
||||
|
||||
void AssetsModule::reloadTexture(const FileWatchInfo &info) {
|
||||
|
|
@ -617,21 +633,22 @@ void AssetsModule::reloadTexture(const FileWatchInfo &info) {
|
|||
|
||||
E2D_LOG_INFO("Reloading texture: {}", info.path);
|
||||
|
||||
Ptr<Texture> newTexture = textureLoader_->load(info.path);
|
||||
if (!newTexture) {
|
||||
// 获取旧纹理指针
|
||||
Texture *oldTexture = textures_.get(info.textureHandle);
|
||||
if (!oldTexture) {
|
||||
E2D_LOG_ERROR("Old texture not found for reload: {}", info.path);
|
||||
return;
|
||||
}
|
||||
|
||||
// 直接在原有纹理对象上重新加载
|
||||
if (!oldTexture->reloadFromFile(info.path)) {
|
||||
E2D_LOG_ERROR("Failed to reload texture: {}", info.path);
|
||||
return;
|
||||
}
|
||||
|
||||
// 替换旧纹理的数据
|
||||
Texture *oldTexture = textures_.get(info.textureHandle);
|
||||
if (oldTexture) {
|
||||
// 这里假设 Texture 类有更新数据的方法
|
||||
// 如果没有,可能需要重新设计
|
||||
E2D_LOG_INFO("Texture reloaded: {}", info.path);
|
||||
E2D_LOG_INFO("Texture reloaded successfully: {}", info.path);
|
||||
notifyTextureReloaded(info.textureHandle);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetsModule::reloadShader(const FileWatchInfo &info) {
|
||||
if (!shaderLoader_ || !info.shaderHandle.isValid()) {
|
||||
|
|
@ -640,6 +657,13 @@ void AssetsModule::reloadShader(const FileWatchInfo &info) {
|
|||
|
||||
E2D_LOG_INFO("Reloading shader: {}", info.path);
|
||||
|
||||
// 获取旧着色器指针
|
||||
Shader *oldShader = shaders_.get(info.shaderHandle);
|
||||
if (!oldShader) {
|
||||
E2D_LOG_ERROR("Old shader not found for reload: {}", info.path);
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找缓存键
|
||||
std::string cacheKey;
|
||||
{
|
||||
|
|
@ -657,29 +681,32 @@ void AssetsModule::reloadShader(const FileWatchInfo &info) {
|
|||
return;
|
||||
}
|
||||
|
||||
// 解析顶点/片段着色器路径
|
||||
// 解析顶点/片段着色器路径并重新加载
|
||||
size_t sepPos = cacheKey.find('|');
|
||||
if (sepPos == std::string::npos) {
|
||||
// 单文件模式
|
||||
Ptr<Shader> newShader = shaderLoader_->load(info.path);
|
||||
if (!newShader) {
|
||||
E2D_LOG_ERROR("Failed to reload shader: {}", info.path);
|
||||
return;
|
||||
}
|
||||
// 单文件模式 - 这里暂不支持,因为 ShaderLoader 目前只支持双文件
|
||||
E2D_LOG_WARN("Single-file shader reload not supported: {}", info.path);
|
||||
} else {
|
||||
// 双文件模式
|
||||
std::string vertPath = cacheKey.substr(0, sepPos);
|
||||
std::string fragPath = cacheKey.substr(sepPos + 1);
|
||||
|
||||
ShaderLoader *loader = static_cast<ShaderLoader *>(shaderLoader_.get());
|
||||
Ptr<Shader> newShader = loader->load(vertPath, fragPath);
|
||||
if (!newShader) {
|
||||
// 读取着色器文件内容
|
||||
std::string vsSource = readFileToString(vertPath);
|
||||
std::string fsSource = readFileToString(fragPath);
|
||||
if (vsSource.empty() || fsSource.empty()) {
|
||||
E2D_LOG_ERROR("Failed to read shader files for reload: {}, {}", vertPath, fragPath);
|
||||
return;
|
||||
}
|
||||
|
||||
// 直接在原有着色器对象上重新加载
|
||||
if (!oldShader->reloadFromSource(vsSource, fsSource)) {
|
||||
E2D_LOG_ERROR("Failed to reload shader: {} + {}", vertPath, fragPath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Shader reloaded: {}", info.path);
|
||||
E2D_LOG_INFO("Shader reloaded successfully: {}", info.path);
|
||||
notifyShaderReloaded(info.shaderHandle);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -105,6 +105,81 @@ uint32_t Shader::getUniformBlockBinding(const std::string &name) const {
|
|||
return UINT32_MAX; // 未找到
|
||||
}
|
||||
|
||||
bool Shader::reloadFromSource(const std::string &vsSource,
|
||||
const std::string &fsSource) {
|
||||
// 创建标准2D顶点布局(位置 + 纹理坐标 + 颜色)
|
||||
VertexLayout vertexLayout;
|
||||
vertexLayout.stride = sizeof(float) * 8; // 2 (pos) + 2 (uv) + 4 (color)
|
||||
vertexLayout.addAttribute(0, VertexFormat::Float2, 0); // 位置
|
||||
vertexLayout.addAttribute(1, VertexFormat::Float2, 8); // 纹理坐标
|
||||
vertexLayout.addAttribute(2, VertexFormat::Float4, 16); // 颜色
|
||||
|
||||
return reloadFromSourceWithLayout(vsSource, fsSource, vertexLayout);
|
||||
}
|
||||
|
||||
bool Shader::reloadFromSourceWithLayout(const std::string &vsSource,
|
||||
const std::string &fsSource,
|
||||
const VertexLayout &vertexLayout) {
|
||||
// 释放旧资源
|
||||
pipeline_ = PipelineHandle();
|
||||
handle_ = ShaderHandle();
|
||||
|
||||
// 获取 RHI 设备
|
||||
auto *rhiModule = RHIModule::get();
|
||||
if (!rhiModule) {
|
||||
E2D_LOG_ERROR("RHIModule not available");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto *device = rhiModule->getDevice();
|
||||
if (!device) {
|
||||
E2D_LOG_ERROR("RHIDevice not available");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 处理源码(添加版本声明)
|
||||
std::string processedVS = addVersionIfNeeded(vsSource, true);
|
||||
std::string processedFS = addVersionIfNeeded(fsSource, false);
|
||||
|
||||
// 创建着色器描述
|
||||
ShaderDesc shaderDesc;
|
||||
shaderDesc.vertexSource = processedVS;
|
||||
shaderDesc.fragmentSource = processedFS;
|
||||
|
||||
// 创建着色器
|
||||
auto shader = device->createShader(shaderDesc);
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to create shader during reload");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取着色器句柄
|
||||
handle_ = ShaderHandle(shader.release());
|
||||
|
||||
// 创建管线描述
|
||||
PipelineDesc pipelineDesc;
|
||||
pipelineDesc.vertexShader = handle_;
|
||||
pipelineDesc.fragmentShader = handle_;
|
||||
pipelineDesc.vertexLayout = vertexLayout;
|
||||
pipelineDesc.blendState = BlendState::alphaBlend();
|
||||
pipelineDesc.depthStencilState = DepthStencilState::noDepthTest();
|
||||
pipelineDesc.rasterizerState = RasterizerState::noCull();
|
||||
|
||||
// 创建管线
|
||||
auto pipeline = device->createPipeline(pipelineDesc);
|
||||
if (!pipeline) {
|
||||
E2D_LOG_ERROR("Failed to create pipeline during reload");
|
||||
handle_ = ShaderHandle();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取管线句柄
|
||||
pipeline_ = PipelineHandle(pipeline.release());
|
||||
|
||||
E2D_LOG_INFO("Shader reloaded successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Shader::addVersionIfNeeded(const std::string &source,
|
||||
bool isVertex) {
|
||||
// 如果已经包含版本声明,直接返回
|
||||
|
|
|
|||
|
|
@ -136,6 +136,81 @@ bool Texture::create(int width, int height, TextureFormat format) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Texture::reloadFromFile(const std::string& path) {
|
||||
// 加载图片
|
||||
int channels;
|
||||
stbi_set_flip_vertically_on_load(true);
|
||||
unsigned char* data = stbi_load(path.c_str(), &width_, &height_, &channels, 0);
|
||||
|
||||
if (!data) {
|
||||
E2D_LOG_ERROR("Failed to reload texture: {}", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 根据通道数确定格式
|
||||
TextureFormat format;
|
||||
switch (channels) {
|
||||
case 1: format = TextureFormat::R8; break;
|
||||
case 2: format = TextureFormat::RG8; break;
|
||||
case 3: format = TextureFormat::RGB8; break;
|
||||
case 4: format = TextureFormat::RGBA8; break;
|
||||
default: format = TextureFormat::RGBA8; break;
|
||||
}
|
||||
|
||||
bool result = reloadFromMemory(data, width_, height_, format);
|
||||
stbi_image_free(data);
|
||||
|
||||
if (result) {
|
||||
E2D_LOG_INFO("Texture reloaded: {} ({}x{})", path, width_, height_);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Texture::reloadFromMemory(const uint8_t* data, int width, int height, TextureFormat format) {
|
||||
// 更新尺寸和格式
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
format_ = format;
|
||||
|
||||
// 获取 RHI 设备
|
||||
auto* rhiModule = RHIModule::get();
|
||||
if (!rhiModule) {
|
||||
E2D_LOG_ERROR("RHIModule not available");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* device = rhiModule->getDevice();
|
||||
if (!device) {
|
||||
E2D_LOG_ERROR("RHIDevice not available");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建新的纹理描述
|
||||
TextureDesc desc;
|
||||
desc.width = static_cast<uint32_t>(width);
|
||||
desc.height = static_cast<uint32_t>(height);
|
||||
desc.format = format;
|
||||
desc.mipLevels = 1;
|
||||
|
||||
// 创建新的 RHI 纹理
|
||||
auto texture = device->createTexture(desc);
|
||||
if (!texture) {
|
||||
E2D_LOG_ERROR("Failed to create RHI texture during reload");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 上传纹理数据
|
||||
if (data) {
|
||||
texture->update(data, static_cast<size_t>(width * height * getBytesPerPixel(format)));
|
||||
}
|
||||
|
||||
// 替换旧的纹理句柄
|
||||
handle_ = TextureHandle(texture.release());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 辅助函数:获取每个像素的字节数
|
||||
uint32_t Texture::getBytesPerPixel(TextureFormat format) {
|
||||
switch (format) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue