refactor(模块系统): 调整模块初始化优先级并重构文件加载逻辑
重构模块初始化顺序,确保文件模块优先加载 移除Shader类中直接文件操作,统一通过FileModule进行文件访问 添加FileModule实现跨平台文件系统操作,支持RomFS 重构资源加载逻辑,统一使用FileModule进行文件操作
This commit is contained in:
parent
8cd883ede7
commit
ec6ced9db2
|
|
@ -33,7 +33,8 @@ namespace extra2d {
|
||||||
* - Handle<T>: 轻量级句柄
|
* - Handle<T>: 轻量级句柄
|
||||||
*/
|
*/
|
||||||
class AssetsModule : public Module {
|
class AssetsModule : public Module {
|
||||||
E2D_REGISTER_MODULE(AssetsModule, "Assets", 2)
|
// 优先级为 3,在 FileModule (优先级 2) 之后初始化
|
||||||
|
E2D_REGISTER_MODULE(AssetsModule, "Assets", 3)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AssetsModule();
|
AssetsModule();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <module/module.h>
|
||||||
|
#include <module/module_registry.h>
|
||||||
|
#include <string>
|
||||||
|
#include <types/base/types.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 文件信息
|
||||||
|
*/
|
||||||
|
struct FileInfo {
|
||||||
|
std::string path;
|
||||||
|
std::string name;
|
||||||
|
bool isDir = false;
|
||||||
|
int64 size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 文件读取结果
|
||||||
|
*/
|
||||||
|
struct FileData {
|
||||||
|
bool ok = false;
|
||||||
|
std::vector<uint8> data;
|
||||||
|
std::string error;
|
||||||
|
|
||||||
|
operator bool() const { return ok; }
|
||||||
|
const uint8 *ptr() const { return data.data(); }
|
||||||
|
size_t size() const { return data.size(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 文件模块
|
||||||
|
*
|
||||||
|
* 提供跨平台文件系统操作
|
||||||
|
* 非单例设计,通过 Context 管理生命周期
|
||||||
|
*/
|
||||||
|
class FileModule : public Module {
|
||||||
|
// 优先级为 2,系统级模块,在 TimerModule (优先级 4) 之前初始化
|
||||||
|
E2D_REGISTER_MODULE(FileModule, "File", 2)
|
||||||
|
|
||||||
|
public:
|
||||||
|
FileModule();
|
||||||
|
~FileModule() override;
|
||||||
|
|
||||||
|
// 禁止拷贝
|
||||||
|
FileModule(const FileModule &) = delete;
|
||||||
|
FileModule &operator=(const FileModule &) = delete;
|
||||||
|
|
||||||
|
// 允许移动
|
||||||
|
FileModule(FileModule &&) noexcept;
|
||||||
|
FileModule &operator=(FileModule &&) noexcept;
|
||||||
|
|
||||||
|
// Module 接口实现
|
||||||
|
bool init() override;
|
||||||
|
void shutdown() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查文件是否存在
|
||||||
|
*/
|
||||||
|
bool exists(const std::string &path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否为目录
|
||||||
|
*/
|
||||||
|
bool isDir(const std::string &path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读取整个文件到内存
|
||||||
|
*/
|
||||||
|
FileData read(const std::string &path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读取文件为字符串
|
||||||
|
*/
|
||||||
|
std::string readString(const std::string &path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 写入数据到文件
|
||||||
|
*/
|
||||||
|
bool write(const std::string &path, const void *data, size_t size) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 写入字符串到文件
|
||||||
|
*/
|
||||||
|
bool writeString(const std::string &path, const std::string &content) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 追加数据到文件
|
||||||
|
*/
|
||||||
|
bool append(const std::string &path, const void *data, size_t size) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 删除文件
|
||||||
|
*/
|
||||||
|
bool remove(const std::string &path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建目录
|
||||||
|
*/
|
||||||
|
bool mkdir(const std::string &path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 列出目录内容
|
||||||
|
*/
|
||||||
|
std::vector<FileInfo> listDir(const std::string &path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取文件大小
|
||||||
|
*/
|
||||||
|
int64 fileSize(const std::string &path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取文件扩展名
|
||||||
|
*/
|
||||||
|
std::string ext(const std::string &path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取文件名(不含路径)
|
||||||
|
*/
|
||||||
|
std::string fileName(const std::string &path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取文件所在目录
|
||||||
|
*/
|
||||||
|
std::string dirName(const std::string &path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 连接路径
|
||||||
|
*/
|
||||||
|
std::string join(const std::string &a, const std::string &b) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取可写目录(用于存档等)
|
||||||
|
*/
|
||||||
|
std::string writableDir() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置资源根目录
|
||||||
|
*/
|
||||||
|
void setAssetRoot(const std::string &root) { assetRoot_ = root; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取资源根目录
|
||||||
|
*/
|
||||||
|
const std::string &assetRoot() const { return assetRoot_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取资源完整路径
|
||||||
|
*/
|
||||||
|
std::string assetPath(const std::string &relPath) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查路径是否为 RomFS 路径
|
||||||
|
* @param path 文件路径
|
||||||
|
* @return 是否以 "romfs:/" 开头
|
||||||
|
*/
|
||||||
|
bool isRomfsPath(const std::string &path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置 RomFS 资源根目录(用于 Switch 平台)
|
||||||
|
* @param root RomFS 根目录,默认为 "romfs:/"
|
||||||
|
*/
|
||||||
|
void setRomfsRoot(const std::string &root) { romfsRoot_ = root; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 RomFS 资源根目录
|
||||||
|
*/
|
||||||
|
const std::string &romfsRoot() const { return romfsRoot_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string assetRoot_;
|
||||||
|
std::string writableDir_;
|
||||||
|
std::string romfsRoot_ = "romfs:/";
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -172,8 +172,8 @@ using TouchCb = std::function<void(const TouchPoint &)>;
|
||||||
* 使用新的 Module 基类,支持自动注册
|
* 使用新的 Module 基类,支持自动注册
|
||||||
*/
|
*/
|
||||||
class InputModule : public Module {
|
class InputModule : public Module {
|
||||||
// 自动注册到模块系统,优先级为 10
|
// 优先级为 7,在 TimerModule (优先级 6) 之后初始化
|
||||||
E2D_REGISTER_MODULE(InputModule, "Input", 10)
|
E2D_REGISTER_MODULE(InputModule, "Input", 7)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
InputModule();
|
InputModule();
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,8 @@ class AssetsModule;
|
||||||
* - 执行实际渲染
|
* - 执行实际渲染
|
||||||
*/
|
*/
|
||||||
class RendererModule : public Module {
|
class RendererModule : public Module {
|
||||||
E2D_REGISTER_MODULE(RendererModule, "Renderer", 3)
|
// 优先级为 4,在 AssetsModule (优先级 3) 之后初始化
|
||||||
|
E2D_REGISTER_MODULE(RendererModule, "Renderer", 4)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -27,14 +27,6 @@ public:
|
||||||
*/
|
*/
|
||||||
~Shader() override;
|
~Shader() override;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从文件加载着色器
|
|
||||||
* @param vsPath 顶点着色器文件路径
|
|
||||||
* @param fsPath 片段着色器文件路径
|
|
||||||
* @return 加载是否成功
|
|
||||||
*/
|
|
||||||
bool loadFromFile(const std::string &vsPath, const std::string &fsPath);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 从源码加载着色器
|
* @brief 从源码加载着色器
|
||||||
* @param vsSource 顶点着色器源码
|
* @param vsSource 顶点着色器源码
|
||||||
|
|
@ -43,14 +35,6 @@ public:
|
||||||
*/
|
*/
|
||||||
bool loadFromSource(const std::string &vsSource, const std::string &fsSource);
|
bool loadFromSource(const std::string &vsSource, const std::string &fsSource);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从文件加载实例化着色器(支持实例属性)
|
|
||||||
* @param vsPath 顶点着色器文件路径
|
|
||||||
* @param fsPath 片段着色器文件路径
|
|
||||||
* @return 加载是否成功
|
|
||||||
*/
|
|
||||||
bool loadInstancedFromFile(const std::string &vsPath, const std::string &fsPath);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 从源码加载实例化着色器(支持实例属性)
|
* @brief 从源码加载实例化着色器(支持实例属性)
|
||||||
* @param vsSource 顶点着色器源码
|
* @param vsSource 顶点着色器源码
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ namespace extra2d {
|
||||||
* 将导演系统集成到引擎模块系统中
|
* 将导演系统集成到引擎模块系统中
|
||||||
*/
|
*/
|
||||||
class SceneModule : public Module {
|
class SceneModule : public Module {
|
||||||
// 自动注册到模块系统,优先级为 4(在 Renderer 之后)
|
// 优先级为 5,在 RendererModule (优先级 4) 之后初始化
|
||||||
E2D_REGISTER_MODULE(SceneModule, "Scene", 4)
|
E2D_REGISTER_MODULE(SceneModule, "Scene", 5)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SceneModule();
|
SceneModule();
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,8 @@ struct TimerInfo {
|
||||||
* 通过事件总线接收更新事件,无需直接依赖
|
* 通过事件总线接收更新事件,无需直接依赖
|
||||||
*/
|
*/
|
||||||
class TimerModule : public Module {
|
class TimerModule : public Module {
|
||||||
// 自动注册到模块系统,优先级为 5(核心模块)
|
// 优先级为 6,在 SceneModule (优先级 5) 之后初始化
|
||||||
E2D_REGISTER_MODULE(TimerModule, "Timer", 5)
|
E2D_REGISTER_MODULE(TimerModule, "Timer", 6)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TimerModule();
|
TimerModule();
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,66 @@
|
||||||
#include <assets/loaders/texture_loader.h>
|
#include <assets/loaders/texture_loader.h>
|
||||||
#include <event/events.h>
|
#include <event/events.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <module/module_registry.h>
|
||||||
|
#include <platform/file_module.h>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <utils/logger.h>
|
#include <utils/logger.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 FileModule 实例
|
||||||
|
*/
|
||||||
|
FileModule *getFileModule() { return getModule<FileModule>(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查路径是否为 RomFS 路径
|
||||||
|
*/
|
||||||
|
bool isRomfsPath(const std::string &path) {
|
||||||
|
FileModule *fileModule = getFileModule();
|
||||||
|
if (fileModule) {
|
||||||
|
return fileModule->isRomfsPath(path);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查文件是否存在(使用 FileModule)
|
||||||
|
*/
|
||||||
|
bool fileExists(const std::string &path) {
|
||||||
|
FileModule *fileModule = getFileModule();
|
||||||
|
if (fileModule) {
|
||||||
|
return fileModule->exists(path);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读取文件内容为字符串(使用 FileModule,支持 RomFS)
|
||||||
|
*/
|
||||||
|
std::string readFileToString(const std::string &path) {
|
||||||
|
FileModule *fileModule = getFileModule();
|
||||||
|
if (fileModule) {
|
||||||
|
return fileModule->readString(path);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取文件最后写入时间(支持 RomFS 路径)
|
||||||
|
*/
|
||||||
|
std::filesystem::file_time_type getLastWriteTime(const std::string &path) {
|
||||||
|
if (isRomfsPath(path)) {
|
||||||
|
// RomFS 文件是只读的,返回一个固定时间
|
||||||
|
return std::filesystem::file_time_type::min();
|
||||||
|
}
|
||||||
|
return std::filesystem::last_write_time(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
AssetsModule *g_assetsModule = nullptr;
|
AssetsModule *g_assetsModule = nullptr;
|
||||||
|
|
||||||
AssetsModule::AssetsModule() { g_assetsModule = this; }
|
AssetsModule::AssetsModule() { g_assetsModule = this; }
|
||||||
|
|
@ -299,7 +354,7 @@ AssetsModule::loadDir<Texture>(const std::string &directory,
|
||||||
|
|
||||||
try {
|
try {
|
||||||
std::filesystem::path dirPath(directory);
|
std::filesystem::path dirPath(directory);
|
||||||
if (!std::filesystem::exists(dirPath)) {
|
if (!fileExists(directory)) {
|
||||||
E2D_LOG_WARN("Directory not found: {}", directory);
|
E2D_LOG_WARN("Directory not found: {}", directory);
|
||||||
return handles;
|
return handles;
|
||||||
}
|
}
|
||||||
|
|
@ -390,27 +445,41 @@ bool AssetsModule::createDefaultResources() {
|
||||||
|
|
||||||
{
|
{
|
||||||
// 从文件加载默认着色器
|
// 从文件加载默认着色器
|
||||||
std::filesystem::path vertPath = "shader/default.vert";
|
// 使用 FileModule 的 assetPath 来获取正确的资源路径(支持 RomFS)
|
||||||
std::filesystem::path fragPath = "shader/default.frag";
|
FileModule *fileModule = getFileModule();
|
||||||
|
std::string vertPath = fileModule
|
||||||
|
? fileModule->assetPath("shader/default.vert")
|
||||||
|
: "shader/default.vert";
|
||||||
|
std::string fragPath = fileModule
|
||||||
|
? fileModule->assetPath("shader/default.frag")
|
||||||
|
: "shader/default.frag";
|
||||||
|
|
||||||
Ptr<Shader> shader = makePtr<Shader>();
|
if (!fileExists(vertPath) || !fileExists(fragPath)) {
|
||||||
|
E2D_LOG_ERROR("Default shader files not found: {}, {}", vertPath,
|
||||||
if (!std::filesystem::exists(vertPath) ||
|
fragPath);
|
||||||
!std::filesystem::exists(fragPath)) {
|
|
||||||
E2D_LOG_ERROR("Default shader files not found: {}, {}", vertPath.string(),
|
|
||||||
fragPath.string());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shader->loadFromFile(vertPath.string(), fragPath.string())) {
|
// 读取着色器文件内容
|
||||||
E2D_LOG_ERROR("Failed to load default shader from files: {}, {}",
|
std::string vsSource = readFileToString(vertPath);
|
||||||
vertPath.string(), fragPath.string());
|
std::string fsSource = readFileToString(fragPath);
|
||||||
|
if (vsSource.empty() || fsSource.empty()) {
|
||||||
|
E2D_LOG_ERROR("Failed to read default shader files: {}, {}", vertPath,
|
||||||
|
fragPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从源码加载着色器
|
||||||
|
Ptr<Shader> shader = makePtr<Shader>();
|
||||||
|
if (!shader->loadFromSource(vsSource, fsSource)) {
|
||||||
|
E2D_LOG_ERROR("Failed to compile default shader: {}, {}", vertPath,
|
||||||
|
fragPath);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultShader_ = shaders_.insert(shader);
|
defaultShader_ = shaders_.insert(shader);
|
||||||
E2D_LOG_DEBUG("Loaded default shader from files: {}, {}", vertPath.string(),
|
E2D_LOG_DEBUG("Loaded default shader from files: {}, {}", vertPath,
|
||||||
fragPath.string());
|
fragPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
@ -505,26 +574,41 @@ Material *AssetsModule::getInstancedMaterialPtr() {
|
||||||
|
|
||||||
bool AssetsModule::createInstancedResources() {
|
bool AssetsModule::createInstancedResources() {
|
||||||
// 加载实例化着色器
|
// 加载实例化着色器
|
||||||
std::filesystem::path vertPath = "shader/sprite_instanced.vert";
|
// 使用 FileModule 的 assetPath 来获取正确的资源路径(支持 RomFS)
|
||||||
std::filesystem::path fragPath = "shader/sprite_instanced.frag";
|
FileModule *fileModule = getFileModule();
|
||||||
|
std::string vertPath =
|
||||||
|
fileModule ? fileModule->assetPath("shader/sprite_instanced.vert")
|
||||||
|
: "shader/sprite_instanced.vert";
|
||||||
|
std::string fragPath =
|
||||||
|
fileModule ? fileModule->assetPath("shader/sprite_instanced.frag")
|
||||||
|
: "shader/sprite_instanced.frag";
|
||||||
|
|
||||||
if (!std::filesystem::exists(vertPath) ||
|
if (!fileExists(vertPath) || !fileExists(fragPath)) {
|
||||||
!std::filesystem::exists(fragPath)) {
|
E2D_LOG_ERROR("Instanced shader files not found: {}, {}", vertPath,
|
||||||
E2D_LOG_ERROR("Instanced shader files not found: {}, {}", vertPath.string(),
|
fragPath);
|
||||||
fragPath.string());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 读取着色器文件内容
|
||||||
|
std::string vsSource = readFileToString(vertPath);
|
||||||
|
std::string fsSource = readFileToString(fragPath);
|
||||||
|
if (vsSource.empty() || fsSource.empty()) {
|
||||||
|
E2D_LOG_ERROR("Failed to read instanced shader files: {}, {}", vertPath,
|
||||||
|
fragPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从源码加载实例化着色器
|
||||||
Ptr<Shader> shader = makePtr<Shader>();
|
Ptr<Shader> shader = makePtr<Shader>();
|
||||||
if (!shader->loadInstancedFromFile(vertPath.string(), fragPath.string())) {
|
if (!shader->loadInstancedFromSource(vsSource, fsSource)) {
|
||||||
E2D_LOG_ERROR("Failed to load instanced shader from files: {}, {}",
|
E2D_LOG_ERROR("Failed to compile instanced shader: {}, {}", vertPath,
|
||||||
vertPath.string(), fragPath.string());
|
fragPath);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
instancedShader_ = shaders_.insert(shader);
|
instancedShader_ = shaders_.insert(shader);
|
||||||
E2D_LOG_DEBUG("Loaded instanced shader from files: {}, {}", vertPath.string(),
|
E2D_LOG_DEBUG("Loaded instanced shader from files: {}, {}", vertPath,
|
||||||
fragPath.string());
|
fragPath);
|
||||||
|
|
||||||
// 创建实例化材质布局
|
// 创建实例化材质布局
|
||||||
Ptr<MaterialLayout> layout = makePtr<MaterialLayout>();
|
Ptr<MaterialLayout> layout = makePtr<MaterialLayout>();
|
||||||
|
|
@ -570,13 +654,13 @@ void AssetsModule::setHotReloadInterval(float interval) {
|
||||||
void AssetsModule::addFileWatch(const std::string &path,
|
void AssetsModule::addFileWatch(const std::string &path,
|
||||||
Handle<Texture> handle) {
|
Handle<Texture> handle) {
|
||||||
try {
|
try {
|
||||||
if (!std::filesystem::exists(path)) {
|
if (!fileExists(path)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileWatchInfo info;
|
FileWatchInfo info;
|
||||||
info.path = path;
|
info.path = path;
|
||||||
info.lastWriteTime = std::filesystem::last_write_time(path);
|
info.lastWriteTime = getLastWriteTime(path);
|
||||||
info.textureHandle = handle;
|
info.textureHandle = handle;
|
||||||
info.isTexture = true;
|
info.isTexture = true;
|
||||||
|
|
||||||
|
|
@ -591,13 +675,13 @@ void AssetsModule::addFileWatch(const std::string &path,
|
||||||
void AssetsModule::addFileWatch(const std::string &path,
|
void AssetsModule::addFileWatch(const std::string &path,
|
||||||
Handle<Shader> handle) {
|
Handle<Shader> handle) {
|
||||||
try {
|
try {
|
||||||
if (!std::filesystem::exists(path)) {
|
if (!fileExists(path)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileWatchInfo info;
|
FileWatchInfo info;
|
||||||
info.path = path;
|
info.path = path;
|
||||||
info.lastWriteTime = std::filesystem::last_write_time(path);
|
info.lastWriteTime = getLastWriteTime(path);
|
||||||
info.shaderHandle = handle;
|
info.shaderHandle = handle;
|
||||||
info.isTexture = false;
|
info.isTexture = false;
|
||||||
|
|
||||||
|
|
@ -691,11 +775,16 @@ void AssetsModule::checkForChanges() {
|
||||||
|
|
||||||
for (auto &info : fileWatchList_) {
|
for (auto &info : fileWatchList_) {
|
||||||
try {
|
try {
|
||||||
if (!std::filesystem::exists(info.path)) {
|
// 跳过 RomFS 路径(只读文件系统)
|
||||||
|
if (isRomfsPath(info.path)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto currentTime = std::filesystem::last_write_time(info.path);
|
if (!fileExists(info.path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto currentTime = getLastWriteTime(info.path);
|
||||||
if (currentTime != info.lastWriteTime) {
|
if (currentTime != info.lastWriteTime) {
|
||||||
info.lastWriteTime = currentTime;
|
info.lastWriteTime = currentTime;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,44 @@
|
||||||
#include <assets/loaders/shader_loader.h>
|
#include <assets/loaders/shader_loader.h>
|
||||||
#include <filesystem>
|
#include <module/module_registry.h>
|
||||||
|
#include <platform/file_module.h>
|
||||||
#include <renderer/shader.h>
|
#include <renderer/shader.h>
|
||||||
#include <utils/logger.h>
|
#include <utils/logger.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 FileModule 实例
|
||||||
|
*/
|
||||||
|
FileModule *getFileModule() {
|
||||||
|
return getModule<FileModule>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查文件是否存在(使用 FileModule)
|
||||||
|
*/
|
||||||
|
bool fileExists(const std::string &path) {
|
||||||
|
FileModule *fileModule = getFileModule();
|
||||||
|
if (fileModule) {
|
||||||
|
return fileModule->exists(path);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读取文件内容为字符串(使用 FileModule,支持 RomFS)
|
||||||
|
*/
|
||||||
|
std::string readFileToString(const std::string &path) {
|
||||||
|
FileModule *fileModule = getFileModule();
|
||||||
|
if (fileModule) {
|
||||||
|
return fileModule->readString(path);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
Ptr<Shader> ShaderLoader::load(const std::string &path) {
|
Ptr<Shader> ShaderLoader::load(const std::string &path) {
|
||||||
std::string basePath = path;
|
std::string basePath = path;
|
||||||
|
|
||||||
|
|
@ -16,10 +50,10 @@ Ptr<Shader> ShaderLoader::load(const std::string &path) {
|
||||||
std::string vertPath = basePath + ".vert";
|
std::string vertPath = basePath + ".vert";
|
||||||
std::string fragPath = basePath + ".frag";
|
std::string fragPath = basePath + ".frag";
|
||||||
|
|
||||||
if (!std::filesystem::exists(vertPath)) {
|
if (!fileExists(vertPath)) {
|
||||||
vertPath = basePath + ".vs";
|
vertPath = basePath + ".vs";
|
||||||
}
|
}
|
||||||
if (!std::filesystem::exists(fragPath)) {
|
if (!fileExists(fragPath)) {
|
||||||
fragPath = basePath + ".fs";
|
fragPath = basePath + ".fs";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,10 +62,24 @@ Ptr<Shader> ShaderLoader::load(const std::string &path) {
|
||||||
|
|
||||||
Ptr<Shader> ShaderLoader::load(const std::string &vertPath,
|
Ptr<Shader> ShaderLoader::load(const std::string &vertPath,
|
||||||
const std::string &fragPath) {
|
const std::string &fragPath) {
|
||||||
Ptr<Shader> shader = makePtr<Shader>();
|
// 读取顶点着色器文件
|
||||||
|
std::string vsSource = readFileToString(vertPath);
|
||||||
|
if (vsSource.empty()) {
|
||||||
|
E2D_LOG_ERROR("ShaderLoader: Failed to read vertex shader: {}", vertPath);
|
||||||
|
return Ptr<Shader>();
|
||||||
|
}
|
||||||
|
|
||||||
if (!shader->loadFromFile(vertPath, fragPath)) {
|
// 读取片段着色器文件
|
||||||
E2D_LOG_ERROR("ShaderLoader: Failed to load shader {} + {}", vertPath,
|
std::string fsSource = readFileToString(fragPath);
|
||||||
|
if (fsSource.empty()) {
|
||||||
|
E2D_LOG_ERROR("ShaderLoader: Failed to read fragment shader: {}", fragPath);
|
||||||
|
return Ptr<Shader>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从源码加载着色器
|
||||||
|
Ptr<Shader> shader = makePtr<Shader>();
|
||||||
|
if (!shader->loadFromSource(vsSource, fsSource)) {
|
||||||
|
E2D_LOG_ERROR("ShaderLoader: Failed to compile shader {} + {}", vertPath,
|
||||||
fragPath);
|
fragPath);
|
||||||
return Ptr<Shader>();
|
return Ptr<Shader>();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,225 @@
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <platform/file_module.h>
|
||||||
|
#include <sstream>
|
||||||
|
#include <utils/logger.h>
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
#include <switch.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
FileModule::FileModule() = default;
|
||||||
|
|
||||||
|
FileModule::~FileModule() = default;
|
||||||
|
|
||||||
|
FileModule::FileModule(FileModule &&) noexcept = default;
|
||||||
|
FileModule &FileModule::operator=(FileModule &&) noexcept = default;
|
||||||
|
|
||||||
|
bool FileModule::init() {
|
||||||
|
writableDir_ = SDL_GetPrefPath("Extra2D", "Extra2D");
|
||||||
|
if (writableDir_.empty()) {
|
||||||
|
writableDir_ = "./";
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
// 初始化 Switch 的 RomFS
|
||||||
|
Result rc = romfsInit();
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
E2D_LOG_INFO("RomFS initialized successfully");
|
||||||
|
// 在 Switch 上设置资源根目录为 RomFS
|
||||||
|
assetRoot_ = "romfs:/";
|
||||||
|
} else {
|
||||||
|
E2D_LOG_WARN("romfsInit failed: {:#08X}", rc);
|
||||||
|
// RomFS 初始化失败,使用当前目录
|
||||||
|
assetRoot_ = "./";
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// 非 Switch 平台,使用当前目录作为资源根目录
|
||||||
|
assetRoot_ = "./";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
E2D_LOG_INFO("Asset root set to: {}", assetRoot_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileModule::shutdown() {
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
// 关闭 RomFS
|
||||||
|
romfsExit();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileModule::isRomfsPath(const std::string &path) const {
|
||||||
|
return path.find(romfsRoot_) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileModule::exists(const std::string &path) const {
|
||||||
|
return fs::exists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileModule::isDir(const std::string &path) const {
|
||||||
|
return fs::is_directory(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileData FileModule::read(const std::string &path) const {
|
||||||
|
FileData result;
|
||||||
|
|
||||||
|
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
result.error = "Cannot open file: " + path;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::streamsize size = file.tellg();
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
result.data.resize(static_cast<size_t>(size));
|
||||||
|
if (!file.read(reinterpret_cast<char *>(result.data.data()), size)) {
|
||||||
|
result.error = "Failed to read file: " + path;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ok = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FileModule::readString(const std::string &path) const {
|
||||||
|
std::ifstream file(path);
|
||||||
|
if (!file.is_open())
|
||||||
|
return "";
|
||||||
|
|
||||||
|
std::stringstream buffer;
|
||||||
|
buffer << file.rdbuf();
|
||||||
|
return buffer.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileModule::write(const std::string &path, const void *data,
|
||||||
|
size_t size) const {
|
||||||
|
// RomFS 是只读的,不允许写入
|
||||||
|
if (isRomfsPath(path)) {
|
||||||
|
E2D_LOG_WARN("Cannot write to RomFS path: {}", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream file(path, std::ios::binary);
|
||||||
|
if (!file.is_open())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
file.write(static_cast<const char *>(data),
|
||||||
|
static_cast<std::streamsize>(size));
|
||||||
|
return file.good();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileModule::writeString(const std::string &path,
|
||||||
|
const std::string &content) const {
|
||||||
|
return write(path, content.data(), content.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileModule::append(const std::string &path, const void *data,
|
||||||
|
size_t size) const {
|
||||||
|
// RomFS 是只读的,不允许写入
|
||||||
|
if (isRomfsPath(path)) {
|
||||||
|
E2D_LOG_WARN("Cannot append to RomFS path: {}", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream file(path, std::ios::binary | std::ios::app);
|
||||||
|
if (!file.is_open())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
file.write(static_cast<const char *>(data),
|
||||||
|
static_cast<std::streamsize>(size));
|
||||||
|
return file.good();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileModule::remove(const std::string &path) const {
|
||||||
|
// RomFS 是只读的,不允许删除
|
||||||
|
if (isRomfsPath(path)) {
|
||||||
|
E2D_LOG_WARN("Cannot remove RomFS path: {}", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return fs::remove(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileModule::mkdir(const std::string &path) const {
|
||||||
|
// RomFS 是只读的,不允许创建目录
|
||||||
|
if (isRomfsPath(path)) {
|
||||||
|
E2D_LOG_WARN("Cannot create directory in RomFS: {}", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs::create_directories(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<FileInfo> FileModule::listDir(const std::string &path) const {
|
||||||
|
std::vector<FileInfo> result;
|
||||||
|
|
||||||
|
if (!fs::exists(path) || !fs::is_directory(path)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &entry : fs::directory_iterator(path)) {
|
||||||
|
std::string name = entry.path().filename().string();
|
||||||
|
if (name == "." || name == "..")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
FileInfo info;
|
||||||
|
info.name = name;
|
||||||
|
info.path = entry.path().string();
|
||||||
|
info.isDir = entry.is_directory();
|
||||||
|
if (!info.isDir && entry.is_regular_file()) {
|
||||||
|
info.size = static_cast<int64>(entry.file_size());
|
||||||
|
}
|
||||||
|
result.push_back(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64 FileModule::fileSize(const std::string &path) const {
|
||||||
|
if (!fs::exists(path) || !fs::is_regular_file(path)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return static_cast<int64>(fs::file_size(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FileModule::ext(const std::string &path) const {
|
||||||
|
fs::path p(path);
|
||||||
|
return p.extension().string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FileModule::fileName(const std::string &path) const {
|
||||||
|
fs::path p(path);
|
||||||
|
return p.filename().string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FileModule::dirName(const std::string &path) const {
|
||||||
|
fs::path p(path);
|
||||||
|
return p.parent_path().string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FileModule::join(const std::string &a, const std::string &b) const {
|
||||||
|
if (a.empty())
|
||||||
|
return b;
|
||||||
|
if (b.empty())
|
||||||
|
return a;
|
||||||
|
|
||||||
|
fs::path base(a);
|
||||||
|
fs::path sub(b);
|
||||||
|
return (base / sub).string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FileModule::writableDir() const { return writableDir_; }
|
||||||
|
|
||||||
|
std::string FileModule::assetPath(const std::string &relPath) const {
|
||||||
|
if (assetRoot_.empty())
|
||||||
|
return relPath;
|
||||||
|
return join(assetRoot_, relPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
#include <fstream>
|
|
||||||
#include <renderer/rhi_module.h>
|
#include <renderer/rhi_module.h>
|
||||||
#include <renderer/shader.h>
|
#include <renderer/shader.h>
|
||||||
#include <sstream>
|
|
||||||
#include <utils/logger.h>
|
#include <utils/logger.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
@ -15,31 +13,6 @@ Shader::~Shader() {
|
||||||
handle_ = ShaderHandle();
|
handle_ = ShaderHandle();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Shader::loadFromFile(const std::string &vsPath,
|
|
||||||
const std::string &fsPath) {
|
|
||||||
// 读取顶点着色器
|
|
||||||
std::ifstream vsFile(vsPath);
|
|
||||||
if (!vsFile.is_open()) {
|
|
||||||
E2D_LOG_ERROR("Failed to open vertex shader: {}", vsPath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::stringstream vsStream;
|
|
||||||
vsStream << vsFile.rdbuf();
|
|
||||||
std::string vsSource = vsStream.str();
|
|
||||||
|
|
||||||
// 读取片段着色器
|
|
||||||
std::ifstream fsFile(fsPath);
|
|
||||||
if (!fsFile.is_open()) {
|
|
||||||
E2D_LOG_ERROR("Failed to open fragment shader: {}", fsPath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::stringstream fsStream;
|
|
||||||
fsStream << fsFile.rdbuf();
|
|
||||||
std::string fsSource = fsStream.str();
|
|
||||||
|
|
||||||
return loadFromSource(vsSource, fsSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Shader::loadFromSource(const std::string &vsSource,
|
bool Shader::loadFromSource(const std::string &vsSource,
|
||||||
const std::string &fsSource) {
|
const std::string &fsSource) {
|
||||||
// 创建标准2D顶点布局(位置 + 纹理坐标 + 颜色)
|
// 创建标准2D顶点布局(位置 + 纹理坐标 + 颜色)
|
||||||
|
|
@ -52,42 +25,17 @@ bool Shader::loadFromSource(const std::string &vsSource,
|
||||||
return loadFromSourceWithLayout(vsSource, fsSource, vertexLayout);
|
return loadFromSourceWithLayout(vsSource, fsSource, vertexLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Shader::loadInstancedFromFile(const std::string &vsPath,
|
|
||||||
const std::string &fsPath) {
|
|
||||||
// 读取顶点着色器
|
|
||||||
std::ifstream vsFile(vsPath);
|
|
||||||
if (!vsFile.is_open()) {
|
|
||||||
E2D_LOG_ERROR("Failed to open instanced vertex shader: {}", vsPath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::stringstream vsStream;
|
|
||||||
vsStream << vsFile.rdbuf();
|
|
||||||
std::string vsSource = vsStream.str();
|
|
||||||
|
|
||||||
// 读取片段着色器
|
|
||||||
std::ifstream fsFile(fsPath);
|
|
||||||
if (!fsFile.is_open()) {
|
|
||||||
E2D_LOG_ERROR("Failed to open instanced fragment shader: {}", fsPath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::stringstream fsStream;
|
|
||||||
fsStream << fsFile.rdbuf();
|
|
||||||
std::string fsSource = fsStream.str();
|
|
||||||
|
|
||||||
return loadInstancedFromSource(vsSource, fsSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Shader::loadInstancedFromSource(const std::string &vsSource,
|
bool Shader::loadInstancedFromSource(const std::string &vsSource,
|
||||||
const std::string &fsSource) {
|
const std::string &fsSource) {
|
||||||
// 创建实例化渲染顶点布局
|
// 创建实例化渲染顶点布局
|
||||||
// 槽位0:顶点属性(位置、UV、颜色)
|
// 槽位0:顶点属性(位置、UV、颜色)
|
||||||
// 槽位1:实例属性(位置、旋转、缩放、颜色、UV区域)
|
// 槽位1:实例属性(位置、旋转、缩放、颜色、UV区域)
|
||||||
VertexLayout vertexLayout;
|
VertexLayout vertexLayout;
|
||||||
|
|
||||||
// 顶点属性(每个顶点)- 槽位0
|
// 顶点属性(每个顶点)- 槽位0
|
||||||
vertexLayout.addAttribute(0, VertexFormat::Float2, 0); // 位置
|
vertexLayout.addAttribute(0, VertexFormat::Float2, 0); // 位置
|
||||||
vertexLayout.addAttribute(1, VertexFormat::Float2, 8); // UV
|
vertexLayout.addAttribute(1, VertexFormat::Float2, 8); // UV
|
||||||
vertexLayout.addAttribute(2, VertexFormat::Float4, 16); // 颜色
|
vertexLayout.addAttribute(2, VertexFormat::Float4, 16); // 颜色
|
||||||
|
|
||||||
// 实例属性(每个实例)- 槽位1
|
// 实例属性(每个实例)- 槽位1
|
||||||
// InstanceData 布局:
|
// InstanceData 布局:
|
||||||
|
|
@ -101,11 +49,16 @@ bool Shader::loadInstancedFromSource(const std::string &vsSource,
|
||||||
// float uvY; // offset 52
|
// float uvY; // offset 52
|
||||||
// float uvWidth; // offset 56
|
// float uvWidth; // offset 56
|
||||||
// float uvHeight; // offset 60
|
// float uvHeight; // offset 60
|
||||||
vertexLayout.addAttribute(VertexAttribute::perInstance(3, VertexFormat::Float2, 0, 1)); // iPosition
|
vertexLayout.addAttribute(
|
||||||
vertexLayout.addAttribute(VertexAttribute::perInstance(4, VertexFormat::Float1, 8, 1)); // iRotation
|
VertexAttribute::perInstance(3, VertexFormat::Float2, 0, 1)); // iPosition
|
||||||
vertexLayout.addAttribute(VertexAttribute::perInstance(5, VertexFormat::Float2, 16, 1)); // iScale
|
vertexLayout.addAttribute(
|
||||||
vertexLayout.addAttribute(VertexAttribute::perInstance(6, VertexFormat::Float4, 32, 1)); // iColor
|
VertexAttribute::perInstance(4, VertexFormat::Float1, 8, 1)); // iRotation
|
||||||
vertexLayout.addAttribute(VertexAttribute::perInstance(7, VertexFormat::Float4, 48, 1)); // iUVRect
|
vertexLayout.addAttribute(
|
||||||
|
VertexAttribute::perInstance(5, VertexFormat::Float2, 16, 1)); // iScale
|
||||||
|
vertexLayout.addAttribute(
|
||||||
|
VertexAttribute::perInstance(6, VertexFormat::Float4, 32, 1)); // iColor
|
||||||
|
vertexLayout.addAttribute(
|
||||||
|
VertexAttribute::perInstance(7, VertexFormat::Float4, 48, 1)); // iUVRect
|
||||||
|
|
||||||
// 注意:stride 在实例化渲染中由 buffer 的 stride 参数控制
|
// 注意:stride 在实例化渲染中由 buffer 的 stride 参数控制
|
||||||
vertexLayout.stride = sizeof(float) * 8; // 顶点缓冲区步长
|
vertexLayout.stride = sizeof(float) * 8; // 顶点缓冲区步长
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue