Compare commits
2 Commits
ea1bbb891d
...
d3a8c6c979
| Author | SHA1 | Date |
|---|---|---|
|
|
d3a8c6c979 | |
|
|
0425425ec7 |
|
|
@ -1,253 +1,143 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/config/app_config.h>
|
||||
#include <extra2d/config/config_manager.h>
|
||||
#include <extra2d/core/export.h>
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/core/module_meta.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/platform/iwindow.h>
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/core/registry.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/config/app_config.h>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class IWindow;
|
||||
class IInput;
|
||||
class RenderBackend;
|
||||
class WindowModule;
|
||||
class RenderModule;
|
||||
class InputModule;
|
||||
|
||||
/**
|
||||
* @brief 应用程序类
|
||||
*
|
||||
* 模块现在通过 E2D_MODULE 宏自动注册,无需手动调用 use()
|
||||
* Application 只负责协调初始化和生命周期管理
|
||||
*/
|
||||
class E2D_API Application {
|
||||
class Application {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return 应用程序实例引用
|
||||
*/
|
||||
static Application &get();
|
||||
|
||||
Application(const Application &) = delete;
|
||||
Application &operator=(const Application &) = delete;
|
||||
|
||||
/**
|
||||
* @brief 使用默认配置初始化
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool init();
|
||||
|
||||
/**
|
||||
* @brief 使用指定配置初始化
|
||||
* @param config 应用配置
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool init(const AppConfig &config);
|
||||
|
||||
/**
|
||||
* @brief 使用配置文件初始化
|
||||
* @param configPath 配置文件路径
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool init(const std::string &configPath);
|
||||
|
||||
/**
|
||||
* @brief 关闭应用程序
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 运行主循环
|
||||
*/
|
||||
void run();
|
||||
|
||||
/**
|
||||
* @brief 请求退出
|
||||
*/
|
||||
void quit();
|
||||
|
||||
/**
|
||||
* @brief 暂停应用程序
|
||||
*/
|
||||
void pause();
|
||||
|
||||
/**
|
||||
* @brief 恢复应用程序
|
||||
*/
|
||||
void resume();
|
||||
|
||||
/**
|
||||
* @brief 检查是否暂停
|
||||
* @return 暂停状态返回 true
|
||||
*/
|
||||
bool isPaused() const { return paused_; }
|
||||
|
||||
/**
|
||||
* @brief 检查是否运行中
|
||||
* @return 运行中返回 true
|
||||
*/
|
||||
bool isRunning() const { return running_; }
|
||||
|
||||
/**
|
||||
* @brief 获取窗口
|
||||
* @return 窗口引用
|
||||
*/
|
||||
IWindow &window() { return *window_; }
|
||||
|
||||
/**
|
||||
* @brief 获取渲染器
|
||||
* @return 渲染器引用
|
||||
*/
|
||||
RenderBackend &renderer();
|
||||
|
||||
/**
|
||||
* @brief 获取输入接口
|
||||
* @return 输入接口引用
|
||||
*/
|
||||
IInput &input();
|
||||
|
||||
/**
|
||||
* @brief 获取场景服务
|
||||
* @return 场景服务共享指针
|
||||
*/
|
||||
SharedPtr<class ISceneService> scenes();
|
||||
|
||||
/**
|
||||
* @brief 获取计时器服务
|
||||
* @return 计时器服务共享指针
|
||||
*/
|
||||
SharedPtr<class ITimerService> timers();
|
||||
|
||||
/**
|
||||
* @brief 获取事件服务
|
||||
* @return 事件服务共享指针
|
||||
*/
|
||||
SharedPtr<class IEventService> events();
|
||||
|
||||
/**
|
||||
* @brief 获取相机服务
|
||||
* @return 相机服务共享指针
|
||||
*/
|
||||
SharedPtr<class ICameraService> camera();
|
||||
|
||||
/**
|
||||
* @brief 进入场景
|
||||
* @param scene 场景指针
|
||||
*/
|
||||
void enterScene(Ptr<class Scene> scene);
|
||||
|
||||
/**
|
||||
* @brief 获取帧间隔时间
|
||||
* @return 帧间隔时间(秒)
|
||||
*/
|
||||
float deltaTime() const { return deltaTime_; }
|
||||
|
||||
/**
|
||||
* @brief 获取总运行时间
|
||||
* @return 总运行时间(秒)
|
||||
*/
|
||||
float totalTime() const { return totalTime_; }
|
||||
|
||||
/**
|
||||
* @brief 获取当前帧率
|
||||
* @return 帧率
|
||||
*/
|
||||
int fps() const { return currentFps_; }
|
||||
|
||||
/**
|
||||
* @brief 获取配置管理器
|
||||
* @return 配置管理器引用
|
||||
*/
|
||||
ConfigManager &config();
|
||||
|
||||
/**
|
||||
* @brief 获取应用配置
|
||||
* @return 应用配置常量引用
|
||||
*/
|
||||
const AppConfig &getConfig() const;
|
||||
|
||||
/**
|
||||
* @brief 获取模块实例(按类型)
|
||||
* @tparam T 模块类型
|
||||
* @return 模块指针,不存在返回 nullptr
|
||||
*/
|
||||
template <typename T> T *getModule() {
|
||||
return ModuleRegistry::instance().getModule<T>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取模块实例(按名称)
|
||||
* @param name 模块名称
|
||||
* @return 模块指针,不存在返回 nullptr
|
||||
*/
|
||||
Module *getModule(const char *name) {
|
||||
return ModuleRegistry::instance().getModule(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册自定义服务
|
||||
* @tparam T 服务接口类型
|
||||
* @param service 服务实例
|
||||
*/
|
||||
template <typename T> void registerService(SharedPtr<T> service) {
|
||||
ServiceLocator::instance().registerService(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取服务
|
||||
* @tparam T 服务接口类型
|
||||
* @return 服务共享指针
|
||||
*/
|
||||
template <typename T> SharedPtr<T> getService() {
|
||||
return ServiceLocator::instance().getService<T>();
|
||||
}
|
||||
static Application& get();
|
||||
|
||||
Application(const Application&) = delete;
|
||||
Application& operator=(const Application&) = delete;
|
||||
|
||||
/**
|
||||
* @brief 注册模块
|
||||
* @tparam T 模块类型
|
||||
* @tparam Args 构造函数参数
|
||||
* @return 模块指针
|
||||
*/
|
||||
template<typename T, typename... Args>
|
||||
T* use(Args&&... args) {
|
||||
return Registry::instance().use<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取模块
|
||||
* @tparam T 模块类型
|
||||
* @return 模块指针
|
||||
*/
|
||||
template<typename T>
|
||||
T* get() const {
|
||||
return Registry::instance().get<T>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool init();
|
||||
|
||||
/**
|
||||
* @brief 初始化(带配置)
|
||||
* @param config 应用配置
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool init(const AppConfig& config);
|
||||
|
||||
/**
|
||||
* @brief 关闭
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 运行主循环
|
||||
*/
|
||||
void run();
|
||||
|
||||
/**
|
||||
* @brief 请求退出
|
||||
*/
|
||||
void quit();
|
||||
|
||||
/**
|
||||
* @brief 暂停
|
||||
*/
|
||||
void pause();
|
||||
|
||||
/**
|
||||
* @brief 恢复
|
||||
*/
|
||||
void resume();
|
||||
|
||||
bool isPaused() const { return paused_; }
|
||||
bool isRunning() const { return running_; }
|
||||
|
||||
/**
|
||||
* @brief 获取窗口
|
||||
* @return 窗口指针
|
||||
*/
|
||||
IWindow* window();
|
||||
|
||||
/**
|
||||
* @brief 获取渲染器
|
||||
* @return 渲染器指针
|
||||
*/
|
||||
RenderBackend* renderer();
|
||||
|
||||
/**
|
||||
* @brief 获取输入
|
||||
* @return 输入指针
|
||||
*/
|
||||
IInput* input();
|
||||
|
||||
/**
|
||||
* @brief 进入场景
|
||||
* @param scene 场景指针
|
||||
*/
|
||||
void enterScene(Ptr<class Scene> scene);
|
||||
|
||||
float deltaTime() const { return deltaTime_; }
|
||||
float totalTime() const { return totalTime_; }
|
||||
int fps() const { return currentFps_; }
|
||||
|
||||
private:
|
||||
Application() = default;
|
||||
~Application();
|
||||
|
||||
/**
|
||||
* @brief 初始化模块(从注册器创建并初始化)
|
||||
* @param config 应用配置
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool initModules(const AppConfig &config);
|
||||
|
||||
/**
|
||||
* @brief 注册核心服务
|
||||
*/
|
||||
void registerCoreServices();
|
||||
void registerCameraService();
|
||||
|
||||
/**
|
||||
* @brief 主循环
|
||||
*/
|
||||
void mainLoop();
|
||||
|
||||
/**
|
||||
* @brief 更新
|
||||
*/
|
||||
void update();
|
||||
|
||||
/**
|
||||
* @brief 渲染
|
||||
*/
|
||||
void render();
|
||||
|
||||
IWindow *window_ = nullptr;
|
||||
|
||||
bool initialized_ = false;
|
||||
bool running_ = false;
|
||||
bool paused_ = false;
|
||||
bool shouldQuit_ = false;
|
||||
|
||||
float deltaTime_ = 0.0f;
|
||||
float totalTime_ = 0.0f;
|
||||
double lastFrameTime_ = 0.0;
|
||||
int frameCount_ = 0;
|
||||
float fpsTimer_ = 0.0f;
|
||||
int currentFps_ = 0;
|
||||
Application();
|
||||
~Application();
|
||||
|
||||
void mainLoop();
|
||||
void update();
|
||||
void render();
|
||||
void registerCoreServices();
|
||||
|
||||
bool initialized_ = false;
|
||||
bool running_ = false;
|
||||
bool paused_ = false;
|
||||
bool shouldQuit_ = false;
|
||||
|
||||
float deltaTime_ = 0.0f;
|
||||
float totalTime_ = 0.0f;
|
||||
double lastFrameTime_ = 0.0;
|
||||
int frameCount_ = 0;
|
||||
float fpsTimer_ = 0.0f;
|
||||
int currentFps_ = 0;
|
||||
|
||||
AppConfig appConfig_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,199 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/config/app_config.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @file config_loader.h
|
||||
* @brief 配置加载器接口
|
||||
*
|
||||
* 负责加载和保存应用级别的配置(AppConfig)。
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief 配置加载结果
|
||||
*/
|
||||
struct ConfigLoadResult {
|
||||
bool success = false;
|
||||
std::string errorMessage;
|
||||
int errorLine = -1;
|
||||
std::string errorField;
|
||||
|
||||
static ConfigLoadResult ok() { return ConfigLoadResult{true, "", -1, ""}; }
|
||||
static ConfigLoadResult error(const std::string& msg, int line = -1, const std::string& field = "") {
|
||||
return ConfigLoadResult{false, msg, line, field};
|
||||
}
|
||||
|
||||
bool isOk() const { return success; }
|
||||
bool hasError() const { return !success; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 配置保存结果
|
||||
*/
|
||||
struct ConfigSaveResult {
|
||||
bool success = false;
|
||||
std::string errorMessage;
|
||||
|
||||
static ConfigSaveResult ok() { return ConfigSaveResult{true, ""}; }
|
||||
static ConfigSaveResult error(const std::string& msg) {
|
||||
return ConfigSaveResult{false, msg};
|
||||
}
|
||||
|
||||
bool isOk() const { return success; }
|
||||
bool hasError() const { return !success; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 配置加载器抽象接口
|
||||
*/
|
||||
class ConfigLoader {
|
||||
public:
|
||||
virtual ~ConfigLoader() = default;
|
||||
|
||||
/**
|
||||
* @brief 从文件加载应用配置
|
||||
* @param filepath 配置文件路径
|
||||
* @param config 输出的配置对象
|
||||
* @return 加载结果
|
||||
*/
|
||||
virtual ConfigLoadResult load(const std::string& filepath, AppConfig& config) = 0;
|
||||
|
||||
/**
|
||||
* @brief 保存应用配置到文件
|
||||
* @param filepath 配置文件路径
|
||||
* @param config 要保存的配置对象
|
||||
* @return 保存结果
|
||||
*/
|
||||
virtual ConfigSaveResult save(const std::string& filepath, const AppConfig& config) = 0;
|
||||
|
||||
/**
|
||||
* @brief 从字符串加载配置
|
||||
* @param content 配置内容字符串
|
||||
* @param config 输出的配置对象
|
||||
* @return 加载结果
|
||||
*/
|
||||
virtual ConfigLoadResult loadFromString(const std::string& content, AppConfig& config) = 0;
|
||||
|
||||
/**
|
||||
* @brief 将配置序列化为字符串
|
||||
* @param config 配置对象
|
||||
* @return 序列化后的字符串
|
||||
*/
|
||||
virtual std::string saveToString(const AppConfig& config) = 0;
|
||||
|
||||
/**
|
||||
* @brief 从文件加载完整配置(包括模块配置)
|
||||
* @param filepath 配置文件路径
|
||||
* @return 加载结果
|
||||
*/
|
||||
virtual ConfigLoadResult loadWithModules(const std::string& filepath) = 0;
|
||||
|
||||
/**
|
||||
* @brief 保存完整配置(包括模块配置)到文件
|
||||
* @param filepath 配置文件路径
|
||||
* @return 保存结果
|
||||
*/
|
||||
virtual ConfigSaveResult saveWithModules(const std::string& filepath) = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取支持的文件扩展名
|
||||
* @return 文件扩展名(不含点号,如 "json")
|
||||
*/
|
||||
virtual const char* extension() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 检查是否支持指定文件
|
||||
* @param filepath 文件路径
|
||||
* @return 如果支持返回 true
|
||||
*/
|
||||
virtual bool supportsFile(const std::string& filepath) const = 0;
|
||||
|
||||
/**
|
||||
* @brief 克隆加载器实例
|
||||
* @return 新的加载器实例
|
||||
*/
|
||||
virtual UniquePtr<ConfigLoader> clone() const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief JSON 配置加载器
|
||||
*/
|
||||
class JsonConfigLoader : public ConfigLoader {
|
||||
public:
|
||||
JsonConfigLoader() = default;
|
||||
~JsonConfigLoader() override = default;
|
||||
|
||||
ConfigLoadResult load(const std::string& filepath, AppConfig& config) override;
|
||||
ConfigSaveResult save(const std::string& filepath, const AppConfig& config) override;
|
||||
ConfigLoadResult loadFromString(const std::string& content, AppConfig& config) override;
|
||||
std::string saveToString(const AppConfig& config) override;
|
||||
ConfigLoadResult loadWithModules(const std::string& filepath) override;
|
||||
ConfigSaveResult saveWithModules(const std::string& filepath) override;
|
||||
const char* extension() const override { return "json"; }
|
||||
bool supportsFile(const std::string& filepath) const override;
|
||||
UniquePtr<ConfigLoader> clone() const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief INI 配置加载器
|
||||
*/
|
||||
class IniConfigLoader : public ConfigLoader {
|
||||
public:
|
||||
IniConfigLoader() = default;
|
||||
~IniConfigLoader() override = default;
|
||||
|
||||
ConfigLoadResult load(const std::string& filepath, AppConfig& config) override;
|
||||
ConfigSaveResult save(const std::string& filepath, const AppConfig& config) override;
|
||||
ConfigLoadResult loadFromString(const std::string& content, AppConfig& config) override;
|
||||
std::string saveToString(const AppConfig& config) override;
|
||||
ConfigLoadResult loadWithModules(const std::string& filepath) override;
|
||||
ConfigSaveResult saveWithModules(const std::string& filepath) override;
|
||||
const char* extension() const override { return "ini"; }
|
||||
bool supportsFile(const std::string& filepath) const override;
|
||||
UniquePtr<ConfigLoader> clone() const override;
|
||||
|
||||
private:
|
||||
std::string sectionKey(const std::string& section, const std::string& key) const;
|
||||
ConfigLoadResult parseInt(const std::string& value, int& result, const std::string& fieldName);
|
||||
ConfigLoadResult parseFloat(const std::string& value, float& result, const std::string& fieldName);
|
||||
ConfigLoadResult parseBool(const std::string& value, bool& result, const std::string& fieldName);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 配置加载器工厂
|
||||
*/
|
||||
class ConfigLoaderFactory {
|
||||
public:
|
||||
/**
|
||||
* @brief 根据文件扩展名创建加载器
|
||||
* @param extension 文件扩展名(不含点号)
|
||||
* @return 配置加载器实例,如果不支持返回 nullptr
|
||||
*/
|
||||
static UniquePtr<ConfigLoader> create(const std::string& extension);
|
||||
|
||||
/**
|
||||
* @brief 根据文件路径创建加载器
|
||||
* @param filepath 文件路径
|
||||
* @return 配置加载器实例,如果不支持返回 nullptr
|
||||
*/
|
||||
static UniquePtr<ConfigLoader> createForFile(const std::string& filepath);
|
||||
|
||||
/**
|
||||
* @brief 检查是否支持指定扩展名
|
||||
* @param extension 文件扩展名
|
||||
* @return 如果支持返回 true
|
||||
*/
|
||||
static bool isExtensionSupported(const std::string& extension);
|
||||
|
||||
/**
|
||||
* @brief 获取所有支持的扩展名
|
||||
* @return 支持的扩展名列表
|
||||
*/
|
||||
static std::vector<std::string> getSupportedExtensions();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,280 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/config/app_config.h>
|
||||
#include <extra2d/config/config_loader.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @file config_manager.h
|
||||
* @brief 配置管理器
|
||||
*
|
||||
* 管理应用级别的配置(AppConfig)。
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief 配置变更事件
|
||||
*/
|
||||
struct ConfigChangeEvent {
|
||||
std::string section;
|
||||
std::string field;
|
||||
std::string oldValue;
|
||||
std::string newValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 配置变更回调类型
|
||||
*/
|
||||
using ConfigChangeCallback = Function<void(const ConfigChangeEvent &)>;
|
||||
|
||||
/**
|
||||
* @brief 配置管理器(单例)
|
||||
*/
|
||||
class ConfigManager {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return 配置管理器实例引用
|
||||
*/
|
||||
static ConfigManager &instance();
|
||||
|
||||
/**
|
||||
* @brief 初始化配置管理器
|
||||
* @param configPath 配置文件路径
|
||||
* @return 如果初始化成功返回 true
|
||||
*/
|
||||
bool initialize(const std::string &configPath = "config.json");
|
||||
|
||||
/**
|
||||
* @brief 关闭配置管理器
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 检查是否已初始化
|
||||
* @return 如果已初始化返回 true
|
||||
*/
|
||||
bool isInitialized() const;
|
||||
|
||||
/**
|
||||
* @brief 加载配置文件
|
||||
* @param filepath 配置文件路径(可选,默认使用初始化时的路径)
|
||||
* @return 加载结果
|
||||
*/
|
||||
ConfigLoadResult loadConfig(const std::string &filepath = "");
|
||||
|
||||
/**
|
||||
* @brief 保存配置到文件
|
||||
* @param filepath 配置文件路径(可选,默认使用初始化时的路径)
|
||||
* @return 保存结果
|
||||
*/
|
||||
ConfigSaveResult saveConfig(const std::string &filepath = "");
|
||||
|
||||
/**
|
||||
* @brief 加载完整配置(包括模块配置)
|
||||
* @param filepath 配置文件路径
|
||||
* @return 加载结果
|
||||
*/
|
||||
ConfigLoadResult loadConfigWithModules(const std::string &filepath = "");
|
||||
|
||||
/**
|
||||
* @brief 保存完整配置(包括模块配置)
|
||||
* @param filepath 配置文件路径
|
||||
* @return 保存结果
|
||||
*/
|
||||
ConfigSaveResult saveConfigWithModules(const std::string &filepath = "");
|
||||
|
||||
/**
|
||||
* @brief 重新加载配置
|
||||
* @return 加载结果
|
||||
*/
|
||||
ConfigLoadResult reload();
|
||||
|
||||
/**
|
||||
* @brief 获取应用配置
|
||||
* @return 应用配置的常量引用
|
||||
*/
|
||||
const AppConfig &appConfig() const;
|
||||
|
||||
/**
|
||||
* @brief 获取可修改的应用配置
|
||||
* @return 应用配置的引用
|
||||
*/
|
||||
AppConfig &appConfig();
|
||||
|
||||
/**
|
||||
* @brief 设置应用配置
|
||||
* @param config 新的配置
|
||||
*/
|
||||
void setAppConfig(const AppConfig &config);
|
||||
|
||||
/**
|
||||
* @brief 注册配置变更回调
|
||||
* @param callback 回调函数
|
||||
* @return 回调ID,用于取消注册
|
||||
*/
|
||||
int registerChangeCallback(ConfigChangeCallback callback);
|
||||
|
||||
/**
|
||||
* @brief 取消注册配置变更回调
|
||||
* @param callbackId 回调ID
|
||||
*/
|
||||
void unregisterChangeCallback(int callbackId);
|
||||
|
||||
/**
|
||||
* @brief 清除所有变更回调
|
||||
*/
|
||||
void clearChangeCallbacks();
|
||||
|
||||
/**
|
||||
* @brief 设置配置值(字符串)
|
||||
* @param section 配置节
|
||||
* @param key 配置键
|
||||
* @param value 配置值
|
||||
*/
|
||||
void setValue(const std::string §ion, const std::string &key,
|
||||
const std::string &value);
|
||||
|
||||
/**
|
||||
* @brief 设置配置值(整数)
|
||||
* @param section 配置节
|
||||
* @param key 配置键
|
||||
* @param value 配置值
|
||||
*/
|
||||
void setValue(const std::string §ion, const std::string &key, int value);
|
||||
|
||||
/**
|
||||
* @brief 设置配置值(浮点数)
|
||||
* @param section 配置节
|
||||
* @param key 配置键
|
||||
* @param value 配置值
|
||||
*/
|
||||
void setValue(const std::string §ion, const std::string &key,
|
||||
float value);
|
||||
|
||||
/**
|
||||
* @brief 设置配置值(布尔值)
|
||||
* @param section 配置节
|
||||
* @param key 配置键
|
||||
* @param value 配置值
|
||||
*/
|
||||
void setValue(const std::string §ion, const std::string &key, bool value);
|
||||
|
||||
/**
|
||||
* @brief 获取配置值(字符串)
|
||||
* @param section 配置节
|
||||
* @param key 配置键
|
||||
* @param defaultValue 默认值
|
||||
* @return 配置值
|
||||
*/
|
||||
std::string getValue(const std::string §ion, const std::string &key,
|
||||
const std::string &defaultValue = "") const;
|
||||
|
||||
/**
|
||||
* @brief 获取配置值(整数)
|
||||
* @param section 配置节
|
||||
* @param key 配置键
|
||||
* @param defaultValue 默认值
|
||||
* @return 配置值
|
||||
*/
|
||||
int getIntValue(const std::string §ion, const std::string &key,
|
||||
int defaultValue = 0) const;
|
||||
|
||||
/**
|
||||
* @brief 获取配置值(浮点数)
|
||||
* @param section 配置节
|
||||
* @param key 配置键
|
||||
* @param defaultValue 默认值
|
||||
* @return 配置值
|
||||
*/
|
||||
float getFloatValue(const std::string §ion, const std::string &key,
|
||||
float defaultValue = 0.0f) const;
|
||||
|
||||
/**
|
||||
* @brief 获取配置值(布尔值)
|
||||
* @param section 配置节
|
||||
* @param key 配置键
|
||||
* @param defaultValue 默认值
|
||||
* @return 配置值
|
||||
*/
|
||||
bool getBoolValue(const std::string §ion, const std::string &key,
|
||||
bool defaultValue = false) const;
|
||||
|
||||
/**
|
||||
* @brief 重置配置到默认值
|
||||
*/
|
||||
void resetToDefaults();
|
||||
|
||||
/**
|
||||
* @brief 检查配置是否有未保存的更改
|
||||
* @return 如果有未保存的更改返回 true
|
||||
*/
|
||||
bool hasUnsavedChanges() const;
|
||||
|
||||
/**
|
||||
* @brief 标记配置为已修改
|
||||
*/
|
||||
void markModified();
|
||||
|
||||
/**
|
||||
* @brief 清除修改标记
|
||||
*/
|
||||
void clearModified();
|
||||
|
||||
/**
|
||||
* @brief 获取配置文件路径
|
||||
* @return 配置文件路径
|
||||
*/
|
||||
const std::string &configPath() const { return m_configPath; }
|
||||
|
||||
/**
|
||||
* @brief 设置自动保存
|
||||
* @param enabled 是否启用自动保存
|
||||
* @param interval 自动保存间隔(秒)
|
||||
*/
|
||||
void setAutoSave(bool enabled, float interval = 30.0f);
|
||||
|
||||
/**
|
||||
* @brief 检查是否启用自动保存
|
||||
* @return 如果启用自动保存返回 true
|
||||
*/
|
||||
bool isAutoSaveEnabled() const { return m_autoSaveEnabled; }
|
||||
|
||||
/**
|
||||
* @brief 更新配置管理器(用于自动保存)
|
||||
* @param deltaTime 帧时间(秒)
|
||||
*/
|
||||
void update(float deltaTime);
|
||||
|
||||
private:
|
||||
ConfigManager();
|
||||
~ConfigManager();
|
||||
ConfigManager(const ConfigManager &) = delete;
|
||||
ConfigManager &operator=(const ConfigManager &) = delete;
|
||||
|
||||
void notifyChangeCallbacks(const ConfigChangeEvent &event);
|
||||
|
||||
AppConfig m_appConfig;
|
||||
UniquePtr<ConfigLoader> m_loader;
|
||||
std::string m_configPath;
|
||||
bool m_initialized = false;
|
||||
bool m_modified = false;
|
||||
mutable std::mutex m_mutex;
|
||||
|
||||
std::unordered_map<int, ConfigChangeCallback> m_changeCallbacks;
|
||||
int m_nextCallbackId = 1;
|
||||
|
||||
std::unordered_map<std::string, std::string> m_rawValues;
|
||||
|
||||
bool m_autoSaveEnabled = false;
|
||||
float m_autoSaveInterval = 30.0f;
|
||||
float m_autoSaveTimer = 0.0f;
|
||||
};
|
||||
|
||||
#define CONFIG_MANAGER ConfigManager::instance()
|
||||
|
||||
}
|
||||
|
|
@ -1,205 +1,76 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/export.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <typeindex>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class Module;
|
||||
class UpdateContext;
|
||||
class RenderContext;
|
||||
/**
|
||||
* @brief 模块上下文基类
|
||||
* 用于遍历模块链,支持链式调用
|
||||
*/
|
||||
class E2D_API ModuleContext {
|
||||
public:
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
virtual ~ModuleContext() = default;
|
||||
|
||||
/**
|
||||
* @brief 遍历下一个模块
|
||||
*/
|
||||
void next();
|
||||
|
||||
/**
|
||||
* @brief 检查是否已完成遍历
|
||||
* @return 完成返回 true
|
||||
*/
|
||||
bool isDone() const { return index_ >= static_cast<int>(modules_.size()); }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param modules 模块列表引用
|
||||
*/
|
||||
ModuleContext(std::vector<Module*>& modules);
|
||||
|
||||
/**
|
||||
* @brief 处理模块
|
||||
* @param m 模块指针
|
||||
*/
|
||||
virtual void handle(Module* m) = 0;
|
||||
|
||||
std::vector<Module*>& modules_;
|
||||
int index_ = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 更新上下文
|
||||
* 用于模块更新阶段
|
||||
*/
|
||||
class UpdateContext : public ModuleContext {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param modules 模块列表引用
|
||||
* @param deltaTime 帧间隔时间
|
||||
*/
|
||||
UpdateContext(std::vector<Module*>& modules, float deltaTime);
|
||||
|
||||
/**
|
||||
* @brief 获取帧间隔时间
|
||||
* @return 帧间隔时间(秒)
|
||||
*/
|
||||
float getDeltaTime() const { return deltaTime_; }
|
||||
|
||||
protected:
|
||||
void handle(Module* m) override;
|
||||
|
||||
private:
|
||||
float deltaTime_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 渲染上下文
|
||||
* 用于模块渲染阶段
|
||||
*/
|
||||
class RenderContext : public ModuleContext {
|
||||
public:
|
||||
/**
|
||||
* @brief 渲染阶段枚举
|
||||
*/
|
||||
enum class Phase {
|
||||
Before, ///< 渲染前
|
||||
On, ///< 渲染时
|
||||
After ///< 渲染后
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param modules 模块列表引用
|
||||
* @param phase 渲染阶段
|
||||
*/
|
||||
RenderContext(std::vector<Module*>& modules, Phase phase);
|
||||
|
||||
/**
|
||||
* @brief 获取渲染阶段
|
||||
* @return 渲染阶段
|
||||
*/
|
||||
Phase getPhase() const { return phase_; }
|
||||
|
||||
protected:
|
||||
void handle(Module* m) override;
|
||||
|
||||
private:
|
||||
Phase phase_;
|
||||
};
|
||||
class Application;
|
||||
|
||||
/**
|
||||
* @brief 模块基类
|
||||
* 所有模块只需继承此类,实现需要的生命周期方法
|
||||
* 所有模块必须继承此类
|
||||
*/
|
||||
class Module {
|
||||
public:
|
||||
/**
|
||||
* @brief 虚析构函数
|
||||
*/
|
||||
virtual ~Module() = default;
|
||||
|
||||
|
||||
/**
|
||||
* @brief 设置模块(初始化)
|
||||
* 在 Application::run() 开始前调用
|
||||
* @brief 初始化模块
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
virtual void setupModule() {}
|
||||
|
||||
virtual bool init() = 0;
|
||||
|
||||
/**
|
||||
* @brief 销毁模块
|
||||
* 在 Application 关闭时调用
|
||||
* @brief 关闭模块
|
||||
*/
|
||||
virtual void destroyModule() {}
|
||||
|
||||
/**
|
||||
* @brief 更新时
|
||||
* 每帧调用
|
||||
* @param ctx 更新上下文
|
||||
*/
|
||||
virtual void onUpdate(UpdateContext& ctx) { ctx.next(); }
|
||||
|
||||
/**
|
||||
* @brief 渲染前
|
||||
* 在渲染开始前调用
|
||||
* @param ctx 渲染上下文
|
||||
*/
|
||||
virtual void beforeRender(RenderContext& ctx) { ctx.next(); }
|
||||
|
||||
/**
|
||||
* @brief 渲染时
|
||||
* 在渲染阶段调用
|
||||
* @param ctx 渲染上下文
|
||||
*/
|
||||
virtual void onRender(RenderContext& ctx) { ctx.next(); }
|
||||
|
||||
/**
|
||||
* @brief 渲染后
|
||||
* 在渲染完成后调用
|
||||
* @param ctx 渲染上下文
|
||||
*/
|
||||
virtual void afterRender(RenderContext& ctx) { ctx.next(); }
|
||||
|
||||
/**
|
||||
* @brief 获取模块名称
|
||||
* @return 模块名称字符串
|
||||
*/
|
||||
virtual const char* getName() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取模块优先级
|
||||
* 数值越小越先执行
|
||||
* @return 优先级值
|
||||
*/
|
||||
virtual int getPriority() const { return 0; }
|
||||
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
/**
|
||||
* @brief 检查模块是否已初始化
|
||||
* @return 已初始化返回 true
|
||||
*/
|
||||
bool isInitialized() const { return initialized_; }
|
||||
virtual bool ok() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取模块名称
|
||||
* @return 模块名称
|
||||
*/
|
||||
virtual const char* name() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取模块优先级(数值越小越优先)
|
||||
* @return 优先级值
|
||||
*/
|
||||
virtual int priority() const { return 100; }
|
||||
|
||||
/**
|
||||
* @brief 获取模块依赖列表
|
||||
* @return 依赖模块类型列表
|
||||
*/
|
||||
virtual std::vector<std::type_index> deps() const { return {}; }
|
||||
|
||||
/**
|
||||
* @brief 设置所属Application
|
||||
* @param app Application指针
|
||||
*/
|
||||
void setApp(class Application* app) { app_ = app; }
|
||||
|
||||
/**
|
||||
* @brief 获取Application
|
||||
* @return Application指针
|
||||
*/
|
||||
class Application* app() const { return app_; }
|
||||
|
||||
protected:
|
||||
friend class Application;
|
||||
|
||||
/**
|
||||
* @brief 设置初始化状态
|
||||
* @param initialized 初始化状态
|
||||
*/
|
||||
void setInitialized(bool initialized) { initialized_ = initialized; }
|
||||
|
||||
bool initialized_ = false;
|
||||
class Application* app_ = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 模块比较函数
|
||||
* 用于按优先级排序模块
|
||||
* @param a 模块a
|
||||
* @param b 模块b
|
||||
* @return a优先级小于b返回true
|
||||
* @brief 模块工厂函数类型
|
||||
*/
|
||||
inline bool modulePriorityCompare(Module* a, Module* b) {
|
||||
return a->getPriority() < b->getPriority();
|
||||
}
|
||||
using ModuleFactory = std::function<UniquePtr<Module>()>;
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <unordered_map>
|
||||
#include <typeindex>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class Application;
|
||||
|
||||
/**
|
||||
* @brief 模块注册表
|
||||
* 管理模块的注册、拓扑排序和生命周期
|
||||
*/
|
||||
class Registry {
|
||||
public:
|
||||
static Registry& instance();
|
||||
|
||||
Registry(const Registry&) = delete;
|
||||
Registry& operator=(const Registry&) = delete;
|
||||
|
||||
/**
|
||||
* @brief 注册模块
|
||||
* @tparam T 模块类型
|
||||
* @tparam Args 构造函数参数类型
|
||||
* @param args 构造函数参数
|
||||
* @return 模块指针
|
||||
*/
|
||||
template<typename T, typename... Args>
|
||||
T* use(Args&&... args) {
|
||||
static_assert(std::is_base_of_v<Module, T>, "T must derive from Module");
|
||||
|
||||
auto typeIdx = std::type_index(typeid(T));
|
||||
if (modules_.count(typeIdx)) {
|
||||
return static_cast<T*>(modules_[typeIdx].get());
|
||||
}
|
||||
|
||||
auto module = makeUnique<T>(std::forward<Args>(args)...);
|
||||
T* ptr = module.get();
|
||||
module->setApp(app_);
|
||||
modules_[typeIdx] = std::move(module);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取模块
|
||||
* @tparam T 模块类型
|
||||
* @return 模块指针,不存在返回 nullptr
|
||||
*/
|
||||
template<typename T>
|
||||
T* get() const {
|
||||
auto typeIdx = std::type_index(typeid(T));
|
||||
auto it = modules_.find(typeIdx);
|
||||
if (it != modules_.end()) {
|
||||
return static_cast<T*>(it->second.get());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取模块(基类版本)
|
||||
* @param typeIdx 类型索引
|
||||
* @return 模块指针
|
||||
*/
|
||||
Module* get(std::type_index typeIdx) const {
|
||||
auto it = modules_.find(typeIdx);
|
||||
if (it != modules_.end()) {
|
||||
return it->second.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置Application
|
||||
*/
|
||||
void setApp(Application* app) { app_ = app; }
|
||||
|
||||
/**
|
||||
* @brief 初始化所有模块(按优先级拓扑排序)
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool init();
|
||||
|
||||
/**
|
||||
* @brief 关闭所有模块
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 清空所有模块
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief 获取模块数量
|
||||
*/
|
||||
size_t size() const { return modules_.size(); }
|
||||
|
||||
private:
|
||||
Registry() = default;
|
||||
~Registry() = default;
|
||||
|
||||
/**
|
||||
* @brief 拓扑排序模块
|
||||
* @return 排序后的模块列表
|
||||
*/
|
||||
std::vector<Module*> topologicalSort();
|
||||
|
||||
std::unordered_map<std::type_index, UniquePtr<Module>> modules_;
|
||||
Application* app_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -6,39 +6,34 @@
|
|||
// Core
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/core/module_macros.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/core/registry.h>
|
||||
|
||||
// Config
|
||||
#include <extra2d/config/app_config.h>
|
||||
#include <extra2d/config/config_loader.h>
|
||||
#include <extra2d/config/config_manager.h>
|
||||
|
||||
// Modules
|
||||
#include <extra2d/modules/config_module.h>
|
||||
#include <extra2d/modules/input_module.h>
|
||||
#include <extra2d/modules/render_module.h>
|
||||
#include <extra2d/modules/window_module.h>
|
||||
#include <extra2d/config/platform_config.h>
|
||||
#include <extra2d/config/platform_detector.h>
|
||||
|
||||
// Platform
|
||||
#include <extra2d/platform/iinput.h>
|
||||
#include <extra2d/platform/iwindow.h>
|
||||
#include <extra2d/platform/keys.h>
|
||||
#include <extra2d/platform/window_config.h>
|
||||
#include <extra2d/platform/input_module.h>
|
||||
#include <extra2d/platform/backend_factory.h>
|
||||
#include <extra2d/platform/window_module.h>
|
||||
|
||||
// Graphics
|
||||
#include <extra2d/graphics/camera.h>
|
||||
#include <extra2d/graphics/font.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/graphics/shader_manager.h>
|
||||
#include <extra2d/graphics/texture.h>
|
||||
|
||||
#include <extra2d/graphics/render_target.h>
|
||||
#include <extra2d/graphics/viewport_adapter.h>
|
||||
#include <extra2d/graphics/vram_manager.h>
|
||||
|
||||
#include <extra2d/graphics/texture_pool.h>
|
||||
#include <extra2d/graphics/camera/camera.h>
|
||||
#include <extra2d/graphics/texture/font.h>
|
||||
#include <extra2d/graphics/core/render_backend.h>
|
||||
#include <extra2d/graphics/core/render_module.h>
|
||||
#include <extra2d/graphics/shader/shader_manager.h>
|
||||
#include <extra2d/graphics/texture/texture.h>
|
||||
#include <extra2d/graphics/core/render_target.h>
|
||||
#include <extra2d/graphics/camera/viewport_adapter.h>
|
||||
#include <extra2d/graphics/memory/vram_manager.h>
|
||||
#include <extra2d/graphics/texture/texture_pool.h>
|
||||
|
||||
// Scene
|
||||
#include <extra2d/scene/node.h>
|
||||
|
|
@ -49,17 +44,19 @@
|
|||
|
||||
// Event
|
||||
#include <extra2d/event/event.h>
|
||||
#include <extra2d/event/event_dispatcher.h>
|
||||
#include <extra2d/event/event_queue.h>
|
||||
|
||||
// Utils
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <extra2d/utils/random.h>
|
||||
#include <extra2d/utils/timer.h>
|
||||
|
||||
// Services
|
||||
#include <extra2d/services/camera_service.h>
|
||||
#include <extra2d/services/event_service.h>
|
||||
#include <extra2d/services/scene_service.h>
|
||||
#include <extra2d/services/timer_service.h>
|
||||
#include <extra2d/services/camera_service.h>
|
||||
#include <extra2d/services/logger_service.h>
|
||||
|
||||
// Application
|
||||
#include <extra2d/app/application.h>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/graphics/texture.h>
|
||||
#include <extra2d/graphics/texture/texture.h>
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <cstdint>
|
||||
#include <variant>
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/graphics/core/render_backend.h>
|
||||
#include <extra2d/platform/window_module.h>
|
||||
#include <typeindex>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 渲染模块
|
||||
* 管理渲染后端
|
||||
*/
|
||||
class RenderModule : public Module {
|
||||
public:
|
||||
/**
|
||||
* @brief 配置结构
|
||||
*/
|
||||
struct Cfg {
|
||||
BackendType backend;
|
||||
int targetFPS;
|
||||
bool vsync;
|
||||
int multisamples;
|
||||
int priority;
|
||||
|
||||
Cfg()
|
||||
: backend(BackendType::OpenGL)
|
||||
, targetFPS(60)
|
||||
, vsync(true)
|
||||
, multisamples(0)
|
||||
, priority(10)
|
||||
{}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param cfg 配置
|
||||
*/
|
||||
explicit RenderModule(const Cfg& cfg = Cfg{});
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~RenderModule() override;
|
||||
|
||||
bool init() override;
|
||||
void shutdown() override;
|
||||
bool ok() const override { return initialized_; }
|
||||
const char* name() const override { return "render"; }
|
||||
int priority() const override { return cfg_.priority; }
|
||||
|
||||
/**
|
||||
* @brief 获取依赖
|
||||
* @return 依赖模块类型列表
|
||||
*/
|
||||
std::vector<std::type_index> deps() const override {
|
||||
return {std::type_index(typeid(WindowModule))};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取渲染器
|
||||
* @return 渲染后端指针
|
||||
*/
|
||||
RenderBackend* renderer() const { return renderer_.get(); }
|
||||
|
||||
private:
|
||||
Cfg cfg_;
|
||||
UniquePtr<RenderBackend> renderer_;
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
||||
#include <extra2d/graphics/texture.h>
|
||||
#include <extra2d/graphics/texture/texture.h>
|
||||
#include <mutex>
|
||||
|
||||
namespace extra2d {
|
||||
|
|
@ -3,9 +3,9 @@
|
|||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/graphics/font.h>
|
||||
#include <extra2d/graphics/texture/font.h>
|
||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
||||
#include <extra2d/graphics/texture.h>
|
||||
#include <extra2d/graphics/texture/texture.h>
|
||||
#include <memory>
|
||||
#include <stb/stb_rect_pack.h>
|
||||
#include <stb/stb_truetype.h>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/graphics/opengl/gl_sprite_batch.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/graphics/shader_interface.h>
|
||||
#include <extra2d/graphics/core/render_backend.h>
|
||||
#include <extra2d/graphics/shader/shader_interface.h>
|
||||
|
||||
#include <array>
|
||||
#include <glad/glad.h>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/graphics/shader_interface.h>
|
||||
#include <extra2d/graphics/shader/shader_interface.h>
|
||||
#include <glad/glad.h>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/graphics/opengl/gl_shader.h>
|
||||
#include <extra2d/graphics/texture.h>
|
||||
#include <extra2d/graphics/texture/texture.h>
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <vector>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/graphics/texture.h>
|
||||
#include <extra2d/graphics/alpha_mask.h>
|
||||
#include <extra2d/graphics/texture/texture.h>
|
||||
#include <extra2d/graphics/texture/alpha_mask.h>
|
||||
|
||||
#include <glad/glad.h>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// Shader缓存条目
|
||||
// ============================================================================
|
||||
struct ShaderCacheEntry {
|
||||
std::string name;
|
||||
std::string sourceHash;
|
||||
uint64_t compileTime = 0;
|
||||
std::vector<uint8_t> binary;
|
||||
std::vector<std::string> dependencies;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Shader缓存管理器
|
||||
// ============================================================================
|
||||
class ShaderCache {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return 缓存管理器实例引用
|
||||
*/
|
||||
static ShaderCache& getInstance();
|
||||
|
||||
/**
|
||||
* @brief 初始化缓存系统
|
||||
* @param cacheDir 缓存目录路径
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
*/
|
||||
bool init(const std::string& cacheDir);
|
||||
|
||||
/**
|
||||
* @brief 关闭缓存系统
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 检查缓存是否有效
|
||||
* @param name Shader名称
|
||||
* @param sourceHash 源码哈希值
|
||||
* @return 缓存有效返回true,否则返回false
|
||||
*/
|
||||
bool hasValidCache(const std::string& name, const std::string& sourceHash);
|
||||
|
||||
/**
|
||||
* @brief 加载缓存的二进制数据
|
||||
* @param name Shader名称
|
||||
* @return 缓存条目指针,不存在返回nullptr
|
||||
*/
|
||||
Ptr<ShaderCacheEntry> loadCache(const std::string& name);
|
||||
|
||||
/**
|
||||
* @brief 保存编译结果到缓存
|
||||
* @param entry 缓存条目
|
||||
* @return 保存成功返回true,失败返回false
|
||||
*/
|
||||
bool saveCache(const ShaderCacheEntry& entry);
|
||||
|
||||
/**
|
||||
* @brief 使缓存失效
|
||||
* @param name Shader名称
|
||||
*/
|
||||
void invalidate(const std::string& name);
|
||||
|
||||
/**
|
||||
* @brief 清除所有缓存
|
||||
*/
|
||||
void clearAll();
|
||||
|
||||
/**
|
||||
* @brief 计算源码哈希值
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return 哈希值字符串
|
||||
*/
|
||||
static std::string computeHash(const std::string& vertSource,
|
||||
const std::string& fragSource);
|
||||
|
||||
/**
|
||||
* @brief 检查是否已初始化
|
||||
* @return 已初始化返回true,否则返回false
|
||||
*/
|
||||
bool isInitialized() const { return initialized_; }
|
||||
|
||||
private:
|
||||
ShaderCache() = default;
|
||||
~ShaderCache() = default;
|
||||
ShaderCache(const ShaderCache&) = delete;
|
||||
ShaderCache& operator=(const ShaderCache&) = delete;
|
||||
|
||||
std::string cacheDir_;
|
||||
std::unordered_map<std::string, ShaderCacheEntry> cacheMap_;
|
||||
bool initialized_ = false;
|
||||
|
||||
/**
|
||||
* @brief 加载缓存索引
|
||||
* @return 加载成功返回true,失败返回false
|
||||
*/
|
||||
bool loadCacheIndex();
|
||||
|
||||
/**
|
||||
* @brief 保存缓存索引
|
||||
* @return 保存成功返回true,失败返回false
|
||||
*/
|
||||
bool saveCacheIndex();
|
||||
|
||||
/**
|
||||
* @brief 获取缓存文件路径
|
||||
* @param name Shader名称
|
||||
* @return 缓存文件完整路径
|
||||
*/
|
||||
std::string getCachePath(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief 确保缓存目录存在
|
||||
* @return 目录存在或创建成功返回true,否则返回false
|
||||
*/
|
||||
bool ensureCacheDirectory();
|
||||
};
|
||||
|
||||
// 便捷宏
|
||||
#define E2D_SHADER_CACHE() ::extra2d::ShaderCache::getInstance()
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 文件变化事件
|
||||
// ============================================================================
|
||||
struct FileChangeEvent {
|
||||
std::string filepath;
|
||||
|
||||
enum class Type {
|
||||
Created,
|
||||
Modified,
|
||||
Deleted,
|
||||
Renamed
|
||||
} type;
|
||||
|
||||
uint64_t timestamp = 0;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 文件变化回调
|
||||
// ============================================================================
|
||||
using FileChangeCallback = std::function<void(const FileChangeEvent&)>;
|
||||
|
||||
// ============================================================================
|
||||
// Shader热重载管理器
|
||||
// ============================================================================
|
||||
class ShaderHotReloader {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return 热重载管理器实例引用
|
||||
*/
|
||||
static ShaderHotReloader& getInstance();
|
||||
|
||||
/**
|
||||
* @brief 初始化热重载系统
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
*/
|
||||
bool init();
|
||||
|
||||
/**
|
||||
* @brief 关闭热重载系统
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 注册Shader文件监视
|
||||
* @param shaderName Shader名称
|
||||
* @param filePaths 要监视的文件列表
|
||||
* @param callback 文件变化时的回调
|
||||
*/
|
||||
void watch(const std::string& shaderName,
|
||||
const std::vector<std::string>& filePaths,
|
||||
FileChangeCallback callback);
|
||||
|
||||
/**
|
||||
* @brief 取消监视
|
||||
* @param shaderName Shader名称
|
||||
*/
|
||||
void unwatch(const std::string& shaderName);
|
||||
|
||||
/**
|
||||
* @brief 更新文件监视(在主循环中调用)
|
||||
*/
|
||||
void update();
|
||||
|
||||
/**
|
||||
* @brief 启用/禁用热重载
|
||||
* @param enabled 是否启用
|
||||
*/
|
||||
void setEnabled(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief 检查是否启用
|
||||
* @return 启用返回true,否则返回false
|
||||
*/
|
||||
bool isEnabled() const { return enabled_; }
|
||||
|
||||
/**
|
||||
* @brief 检查是否已初始化
|
||||
* @return 已初始化返回true,否则返回false
|
||||
*/
|
||||
bool isInitialized() const { return initialized_; }
|
||||
|
||||
private:
|
||||
ShaderHotReloader() = default;
|
||||
~ShaderHotReloader() = default;
|
||||
ShaderHotReloader(const ShaderHotReloader&) = delete;
|
||||
ShaderHotReloader& operator=(const ShaderHotReloader&) = delete;
|
||||
|
||||
bool enabled_ = false;
|
||||
bool initialized_ = false;
|
||||
|
||||
struct WatchInfo {
|
||||
std::vector<std::string> filePaths;
|
||||
FileChangeCallback callback;
|
||||
std::unordered_map<std::string, uint64_t> modifiedTimes;
|
||||
};
|
||||
std::unordered_map<std::string, WatchInfo> watchMap_;
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE watchHandle_ = nullptr;
|
||||
std::vector<uint8_t> buffer_;
|
||||
std::string watchDir_;
|
||||
bool watching_ = false;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 轮询检查文件变化
|
||||
*/
|
||||
void pollChanges();
|
||||
|
||||
/**
|
||||
* @brief 获取文件修改时间
|
||||
* @param filepath 文件路径
|
||||
* @return 修改时间戳
|
||||
*/
|
||||
static uint64_t getFileModifiedTime(const std::string& filepath);
|
||||
};
|
||||
|
||||
// 便捷宏
|
||||
#define E2D_SHADER_HOT_RELOADER() ::extra2d::ShaderHotReloader::getInstance()
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,52 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/graphics/shader_interface.h>
|
||||
#include <extra2d/graphics/shader_loader.h>
|
||||
#include <extra2d/config/platform_detector.h>
|
||||
#include <extra2d/graphics/shader/shader_cache.h>
|
||||
#include <extra2d/graphics/shader/shader_hot_reloader.h>
|
||||
#include <extra2d/graphics/shader/shader_interface.h>
|
||||
#include <extra2d/graphics/shader/shader_loader.h>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// Shader缓存条目
|
||||
// ============================================================================
|
||||
struct ShaderCacheEntry {
|
||||
std::string name;
|
||||
std::string sourceHash;
|
||||
uint64_t compileTime = 0;
|
||||
std::vector<uint8_t> binary;
|
||||
std::vector<std::string> dependencies;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 文件变化事件
|
||||
// ============================================================================
|
||||
struct FileChangeEvent {
|
||||
std::string filepath;
|
||||
|
||||
enum class Type {
|
||||
Created,
|
||||
Modified,
|
||||
Deleted,
|
||||
Renamed
|
||||
} type;
|
||||
|
||||
uint64_t timestamp = 0;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Shader重载回调
|
||||
// ============================================================================
|
||||
using ShaderReloadCallback = std::function<void(Ptr<IShader> newShader)>;
|
||||
using FileChangeCallback = std::function<void(const FileChangeEvent&)>;
|
||||
|
||||
// ============================================================================
|
||||
// Shader管理器 - 统一入口(加载/缓存/热重载)
|
||||
// Shader管理器 - 统一入口
|
||||
// ============================================================================
|
||||
class ShaderManager {
|
||||
public:
|
||||
|
|
@ -62,6 +32,7 @@ public:
|
|||
|
||||
/**
|
||||
* @brief 使用平台默认路径初始化Shader系统
|
||||
* 自动检测平台并使用正确的路径(romfs/sdmc/相对路径)
|
||||
* @param factory 渲染后端Shader工厂
|
||||
* @param appName 应用名称(用于缓存目录)
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
|
|
@ -86,11 +57,14 @@ public:
|
|||
|
||||
/**
|
||||
* @brief 检查是否已初始化
|
||||
* @return 已初始化返回true,否则返回false
|
||||
*/
|
||||
bool isInitialized() const { return initialized_; }
|
||||
|
||||
/**
|
||||
* @brief 检查当前平台是否支持热重载
|
||||
* Switch平台使用romfs,不支持热重载
|
||||
* @return 支持热重载返回true
|
||||
*/
|
||||
bool isHotReloadSupported() const { return hotReloadSupported_; }
|
||||
|
||||
|
|
@ -98,73 +72,134 @@ public:
|
|||
// Shader加载
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 从分离文件加载Shader
|
||||
* @param name Shader名称
|
||||
* @param vertPath 顶点着色器文件路径
|
||||
* @param fragPath 片段着色器文件路径
|
||||
* @return 加载的Shader实例
|
||||
*/
|
||||
Ptr<IShader> loadFromFiles(const std::string& name,
|
||||
const std::string& vertPath,
|
||||
const std::string& fragPath);
|
||||
|
||||
/**
|
||||
* @brief 从组合文件加载Shader
|
||||
* @param path 组合Shader文件路径
|
||||
* @return 加载的Shader实例
|
||||
*/
|
||||
Ptr<IShader> loadFromCombinedFile(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 从源码加载Shader
|
||||
* @param name Shader名称
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return 加载的Shader实例
|
||||
*/
|
||||
Ptr<IShader> loadFromSource(const std::string& name,
|
||||
const std::string& vertSource,
|
||||
const std::string& fragSource);
|
||||
|
||||
/**
|
||||
* @brief 获取已加载的Shader
|
||||
* @param name Shader名称
|
||||
* @return Shader实例,不存在返回nullptr
|
||||
*/
|
||||
Ptr<IShader> get(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief 检查Shader是否存在
|
||||
* @param name Shader名称
|
||||
* @return 存在返回true,否则返回false
|
||||
*/
|
||||
bool has(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief 移除Shader
|
||||
* @param name Shader名称
|
||||
*/
|
||||
void remove(const std::string& name);
|
||||
|
||||
/**
|
||||
* @brief 清除所有Shader
|
||||
*/
|
||||
void clear();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 热重载
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 注册重载回调
|
||||
* @param name Shader名称
|
||||
* @param callback 重载回调函数
|
||||
*/
|
||||
void setReloadCallback(const std::string& name, ShaderReloadCallback callback);
|
||||
|
||||
/**
|
||||
* @brief 启用/禁用热重载
|
||||
* @param enabled 是否启用
|
||||
*/
|
||||
void setHotReloadEnabled(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief 检查热重载是否启用
|
||||
* @return 启用返回true,否则返回false
|
||||
*/
|
||||
bool isHotReloadEnabled() const;
|
||||
|
||||
/**
|
||||
* @brief 更新热重载系统(主循环调用)
|
||||
*/
|
||||
void update();
|
||||
|
||||
/**
|
||||
* @brief 手动重载Shader
|
||||
* @param name Shader名称
|
||||
* @return 重载成功返回true,失败返回false
|
||||
*/
|
||||
bool reload(const std::string& name);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 热重载文件监视
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
void watch(const std::string& shaderName,
|
||||
const std::vector<std::string>& filePaths,
|
||||
FileChangeCallback callback);
|
||||
void unwatch(const std::string& shaderName);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 内置Shader
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取内置Shader
|
||||
* @param name 内置Shader名称
|
||||
* @return Shader实例
|
||||
*/
|
||||
Ptr<IShader> getBuiltin(const std::string& name);
|
||||
|
||||
/**
|
||||
* @brief 加载所有内置Shader
|
||||
* @return 加载成功返回true,失败返回false
|
||||
*/
|
||||
bool loadBuiltinShaders();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 工具方法
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取Shader目录
|
||||
* @return Shader目录路径
|
||||
*/
|
||||
const std::string& getShaderDir() const { return shaderDir_; }
|
||||
|
||||
/**
|
||||
* @brief 获取ShaderLoader
|
||||
* @return ShaderLoader引用
|
||||
*/
|
||||
ShaderLoader& getLoader() { return loader_; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 缓存
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
bool hasValidCache(const std::string& name, const std::string& sourceHash);
|
||||
Ptr<ShaderCacheEntry> loadCache(const std::string& name);
|
||||
bool saveCache(const ShaderCacheEntry& entry);
|
||||
void invalidateCache(const std::string& name);
|
||||
void clearAllCache();
|
||||
static std::string computeHash(const std::string& vertSource,
|
||||
const std::string& fragSource);
|
||||
|
||||
private:
|
||||
ShaderManager() = default;
|
||||
~ShaderManager() = default;
|
||||
ShaderManager(const ShaderManager&) = delete;
|
||||
ShaderManager& operator=(const ShaderManager&) = delete;
|
||||
|
||||
// Shader存储
|
||||
std::string shaderDir_;
|
||||
std::string cacheDir_;
|
||||
Ptr<IShaderFactory> factory_;
|
||||
|
|
@ -184,45 +219,29 @@ private:
|
|||
bool hotReloadEnabled_ = false;
|
||||
bool hotReloadSupported_ = true;
|
||||
|
||||
// 缓存(原 ShaderCache)
|
||||
std::unordered_map<std::string, ShaderCacheEntry> cacheMap_;
|
||||
bool cacheInitialized_ = false;
|
||||
|
||||
bool initCache(const std::string& cacheDir);
|
||||
void shutdownCache();
|
||||
bool loadCacheIndex();
|
||||
bool saveCacheIndex();
|
||||
std::string getCachePath(const std::string& name) const;
|
||||
bool ensureCacheDirectory();
|
||||
|
||||
// 热重载(原 ShaderHotReloader)
|
||||
bool reloaderInitialized_ = false;
|
||||
|
||||
struct WatchInfo {
|
||||
std::vector<std::string> filePaths;
|
||||
FileChangeCallback callback;
|
||||
std::unordered_map<std::string, uint64_t> modifiedTimes;
|
||||
};
|
||||
std::unordered_map<std::string, WatchInfo> watchMap_;
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE watchHandle_ = nullptr;
|
||||
std::vector<uint8_t> watchBuffer_;
|
||||
std::string watchDir_;
|
||||
bool watching_ = false;
|
||||
#endif
|
||||
|
||||
bool initReloader();
|
||||
void shutdownReloader();
|
||||
void pollChanges();
|
||||
static uint64_t getFileModifiedTime(const std::string& filepath);
|
||||
|
||||
/**
|
||||
* @brief 从缓存加载Shader
|
||||
* @param name Shader名称
|
||||
* @param sourceHash 源码哈希值
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return Shader实例
|
||||
*/
|
||||
Ptr<IShader> loadFromCache(const std::string& name,
|
||||
const std::string& sourceHash,
|
||||
const std::string& vertSource,
|
||||
const std::string& fragSource);
|
||||
|
||||
/**
|
||||
* @brief 创建内置Shader源码
|
||||
*/
|
||||
void createBuiltinShaderSources();
|
||||
|
||||
/**
|
||||
* @brief 处理文件变化事件
|
||||
* @param shaderName Shader名称
|
||||
* @param event 文件变化事件
|
||||
*/
|
||||
void handleFileChange(const std::string& shaderName, const FileChangeEvent& event);
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/graphics/shader/shader_interface.h>
|
||||
#include <glm/vec4.hpp>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
struct WaterParams {
|
||||
float waveSpeed = 1.0f;
|
||||
float waveAmplitude = 0.02f;
|
||||
float waveFrequency = 4.0f;
|
||||
};
|
||||
|
||||
struct OutlineParams {
|
||||
Color color = Colors::Black;
|
||||
float thickness = 2.0f;
|
||||
};
|
||||
|
||||
struct DistortionParams {
|
||||
float distortionAmount = 0.02f;
|
||||
float timeScale = 1.0f;
|
||||
};
|
||||
|
||||
struct PixelateParams {
|
||||
float pixelSize = 8.0f;
|
||||
};
|
||||
|
||||
struct InvertParams {
|
||||
float strength = 1.0f;
|
||||
};
|
||||
|
||||
struct GrayscaleParams {
|
||||
float intensity = 1.0f;
|
||||
};
|
||||
|
||||
struct BlurParams {
|
||||
float radius = 5.0f;
|
||||
};
|
||||
|
||||
class ShaderPreset {
|
||||
public:
|
||||
/**
|
||||
* @brief 创建水波纹效果着色器
|
||||
* @param params 水波纹效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
static Ptr<IShader> Water(const WaterParams& params = {});
|
||||
|
||||
/**
|
||||
* @brief 创建描边效果着色器
|
||||
* @param params 描边效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
static Ptr<IShader> Outline(const OutlineParams& params = {});
|
||||
|
||||
/**
|
||||
* @brief 创建扭曲效果着色器
|
||||
* @param params 扭曲效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
static Ptr<IShader> Distortion(const DistortionParams& params = {});
|
||||
|
||||
/**
|
||||
* @brief 创建像素化效果着色器
|
||||
* @param params 像素化效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
static Ptr<IShader> Pixelate(const PixelateParams& params = {});
|
||||
|
||||
/**
|
||||
* @brief 创建反相效果着色器
|
||||
* @param params 反相效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
static Ptr<IShader> Invert(const InvertParams& params = {});
|
||||
|
||||
/**
|
||||
* @brief 创建灰度效果着色器
|
||||
* @param params 灰度效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
static Ptr<IShader> Grayscale(const GrayscaleParams& params = {});
|
||||
|
||||
/**
|
||||
* @brief 创建模糊效果着色器
|
||||
* @param params 模糊效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
static Ptr<IShader> Blur(const BlurParams& params = {});
|
||||
|
||||
/**
|
||||
* @brief 创建灰度+描边组合效果着色器
|
||||
* @param grayParams 灰度效果参数
|
||||
* @param outlineParams 描边效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
static Ptr<IShader> GrayscaleOutline(const GrayscaleParams& grayParams,
|
||||
const OutlineParams& outlineParams);
|
||||
|
||||
/**
|
||||
* @brief 创建像素化+反相组合效果着色器
|
||||
* @param pixParams 像素化效果参数
|
||||
* @param invParams 反相效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
static Ptr<IShader> PixelateInvert(const PixelateParams& pixParams,
|
||||
const InvertParams& invParams);
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/graphics/texture.h>
|
||||
#include <extra2d/graphics/texture/texture.h>
|
||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/graphics/texture.h>
|
||||
#include <extra2d/graphics/texture/texture.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @file input_config.h
|
||||
* @brief 输入模块配置
|
||||
*
|
||||
* 定义输入相关的配置数据结构,由 InputModule 管理。
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief 输入配置数据结构
|
||||
*/
|
||||
struct InputConfigData {
|
||||
bool enabled = true;
|
||||
bool rawMouseInput = false;
|
||||
float mouseSensitivity = 1.0f;
|
||||
bool invertMouseY = false;
|
||||
bool invertMouseX = false;
|
||||
float deadzone = 0.15f;
|
||||
float triggerThreshold = 0.5f;
|
||||
bool enableVibration = true;
|
||||
int maxGamepads = 4;
|
||||
bool autoConnectGamepads = true;
|
||||
std::string gamepadMappingFile;
|
||||
|
||||
/**
|
||||
* @brief 验证死区值是否有效
|
||||
* @return 如果死区值在0-1范围内返回 true
|
||||
*/
|
||||
bool isDeadzoneValid() const { return deadzone >= 0.0f && deadzone <= 1.0f; }
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/event/event_dispatcher.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/graphics/core/render_backend.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/export.h>
|
||||
#include <extra2d/graphics/camera.h>
|
||||
#include <extra2d/graphics/camera/camera.h>
|
||||
#include <extra2d/scene/node.h>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -14,7 +13,7 @@ struct RenderCommand;
|
|||
// ============================================================================
|
||||
// 场景类 - 节点容器,管理整个场景图
|
||||
// ============================================================================
|
||||
class E2D_API Scene : public Node {
|
||||
class Scene : public Node {
|
||||
public:
|
||||
Scene();
|
||||
~Scene() override = default;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/graphics/texture.h>
|
||||
#include <extra2d/graphics/texture/texture.h>
|
||||
#include <extra2d/scene/node.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ public:
|
|||
protected:
|
||||
void onTransitionStart() override;
|
||||
void renderContent(RenderBackend &renderer) override;
|
||||
void updateTransition(float dt);
|
||||
|
||||
private:
|
||||
int divisions_;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,12 @@ protected:
|
|||
*/
|
||||
void onTransitionStart() override;
|
||||
|
||||
/**
|
||||
* @brief 更新过渡进度
|
||||
* @param dt 帧间隔时间(秒)
|
||||
*/
|
||||
void updateTransition(float dt) override;
|
||||
|
||||
/**
|
||||
* @brief 渲染内容
|
||||
* 根据进度控制新旧场景的显示
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ public:
|
|||
protected:
|
||||
void onTransitionStart() override;
|
||||
void renderContent(RenderBackend &renderer) override;
|
||||
void updateTransition(float dt);
|
||||
|
||||
private:
|
||||
Axis axis_;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ public:
|
|||
protected:
|
||||
void onTransitionStart() override;
|
||||
void renderContent(RenderBackend &renderer) override;
|
||||
void updateTransition(float dt);
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -80,11 +80,22 @@ public:
|
|||
*/
|
||||
bool isFinished() const { return isFinished_; }
|
||||
|
||||
/**
|
||||
* @brief 是否已取消
|
||||
*/
|
||||
bool isCancelled() const { return isCancelled_; }
|
||||
|
||||
/**
|
||||
* @brief 完成过渡,通知 SceneManager 切换到目标场景
|
||||
*/
|
||||
void finish();
|
||||
|
||||
/**
|
||||
* @brief 取消过渡
|
||||
* @param immediate 是否立即完成(false则回滚到原场景)
|
||||
*/
|
||||
void cancel(bool immediate = false);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 渲染 - 在 TransitionScene 上渲染新旧两个子场景
|
||||
// ------------------------------------------------------------------------
|
||||
|
|
@ -113,15 +124,25 @@ protected:
|
|||
*/
|
||||
virtual void drawInScene(RenderBackend &renderer);
|
||||
|
||||
/**
|
||||
* @brief 更新过渡进度(子类重写此方法更新动画)
|
||||
* @param dt 帧间隔时间(秒)
|
||||
*/
|
||||
virtual void updateTransition(float dt);
|
||||
|
||||
float duration_;
|
||||
float elapsed_ = 0.0f;
|
||||
float progress_ = 0.0f;
|
||||
bool isFinished_ = false;
|
||||
bool isCancelled_ = false;
|
||||
|
||||
Ptr<Scene> inScene_; // 要进入的场景
|
||||
Ptr<Scene> outScene_; // 要退出的场景
|
||||
|
||||
FinishCallback finishCallback_;
|
||||
FinishCallback cancelCallback_; // 取消回调
|
||||
|
||||
friend class SceneManager;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ public:
|
|||
protected:
|
||||
void onTransitionStart() override;
|
||||
void renderContent(RenderBackend &renderer) override;
|
||||
void updateTransition(float dt);
|
||||
|
||||
private:
|
||||
TransitionDirection direction_;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/service_interface.h>
|
||||
#include <extra2d/graphics/camera.h>
|
||||
#include <extra2d/graphics/viewport_adapter.h>
|
||||
#include <extra2d/graphics/camera/camera.h>
|
||||
#include <extra2d/graphics/camera/viewport_adapter.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,228 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/service_interface.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <cstdarg>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 日志级别枚举
|
||||
*/
|
||||
enum class LogLevel {
|
||||
Trace = 0,
|
||||
Debug = 1,
|
||||
Info = 2,
|
||||
Warn = 3,
|
||||
Error = 4,
|
||||
Fatal = 5,
|
||||
Off = 6
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 日志服务接口
|
||||
*/
|
||||
class ILogger : public IService {
|
||||
public:
|
||||
virtual ~ILogger() = default;
|
||||
|
||||
/**
|
||||
* @brief 设置日志级别
|
||||
*/
|
||||
virtual void setLevel(LogLevel level) = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取日志级别
|
||||
*/
|
||||
virtual LogLevel getLevel() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 检查日志级别是否启用
|
||||
*/
|
||||
virtual bool isEnabled(LogLevel level) const = 0;
|
||||
|
||||
/**
|
||||
* @brief 记录日志(格式化)
|
||||
*/
|
||||
virtual void log(LogLevel level, const char* fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief 记录日志(字符串)
|
||||
*/
|
||||
virtual void log(LogLevel level, const std::string& msg) = 0;
|
||||
|
||||
/**
|
||||
* @brief Trace级别日志
|
||||
*/
|
||||
virtual void trace(const char* fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief Debug级别日志
|
||||
*/
|
||||
virtual void debug(const char* fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief Info级别日志
|
||||
*/
|
||||
virtual void info(const char* fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief Warn级别日志
|
||||
*/
|
||||
virtual void warn(const char* fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief Error级别日志
|
||||
*/
|
||||
virtual void error(const char* fmt, ...) = 0;
|
||||
|
||||
/**
|
||||
* @brief Fatal级别日志
|
||||
*/
|
||||
virtual void fatal(const char* fmt, ...) = 0;
|
||||
|
||||
ServiceInfo getServiceInfo() const override {
|
||||
ServiceInfo info;
|
||||
info.name = "Logger";
|
||||
info.priority = ServicePriority::Core;
|
||||
info.enabled = true;
|
||||
return info;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 控制台日志服务实现
|
||||
*/
|
||||
class ConsoleLogger : public ILogger {
|
||||
public:
|
||||
ConsoleLogger();
|
||||
~ConsoleLogger() override;
|
||||
|
||||
bool initialize() override;
|
||||
void shutdown() override;
|
||||
|
||||
void setLevel(LogLevel level) override;
|
||||
LogLevel getLevel() const override;
|
||||
bool isEnabled(LogLevel level) const override;
|
||||
|
||||
void log(LogLevel level, const char* fmt, ...) override;
|
||||
void log(LogLevel level, const std::string& msg) override;
|
||||
|
||||
void trace(const char* fmt, ...) override;
|
||||
void debug(const char* fmt, ...) override;
|
||||
void info(const char* fmt, ...) override;
|
||||
void warn(const char* fmt, ...) override;
|
||||
void error(const char* fmt, ...) override;
|
||||
void fatal(const char* fmt, ...) override;
|
||||
|
||||
private:
|
||||
void output(LogLevel level, const char* msg);
|
||||
const char* getLevelString(LogLevel level);
|
||||
|
||||
LogLevel level_;
|
||||
class Impl;
|
||||
UniquePtr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
// 格式化辅助函数 - 将参数转换为字符串
|
||||
namespace extra2d {
|
||||
namespace detail {
|
||||
template<typename T>
|
||||
std::string to_string(T&& value) {
|
||||
using Decayed = std::decay_t<T>;
|
||||
if constexpr (std::is_same_v<Decayed, std::string>) {
|
||||
return value;
|
||||
} else if constexpr (std::is_same_v<Decayed, const char*>) {
|
||||
return value ? value : "(null)";
|
||||
} else if constexpr (std::is_arithmetic_v<Decayed>) {
|
||||
if constexpr (std::is_same_v<Decayed, bool>) {
|
||||
return value ? "true" : "false";
|
||||
} else if constexpr (std::is_floating_point_v<Decayed>) {
|
||||
return std::to_string(value);
|
||||
} else {
|
||||
return std::to_string(value);
|
||||
}
|
||||
} else {
|
||||
return "<?>";
|
||||
}
|
||||
}
|
||||
|
||||
inline void format_impl(std::string& result, const char* fmt) {
|
||||
result += fmt;
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
void format_impl(std::string& result, const char* fmt, T&& value, Args&&... args) {
|
||||
const char* p = fmt;
|
||||
while (*p) {
|
||||
if (*p == '{' && *(p + 1) == '}') {
|
||||
result += to_string(std::forward<T>(value));
|
||||
format_impl(result, p + 2, std::forward<Args>(args)...);
|
||||
return;
|
||||
}
|
||||
result += *p++;
|
||||
}
|
||||
// 没有更多的 {},追加剩余参数(不应该发生)
|
||||
result += " ";
|
||||
result += to_string(std::forward<T>(value));
|
||||
format_impl(result, p, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
std::string format_str(const char* fmt, Args&&... args) {
|
||||
if constexpr (sizeof...(args) == 0) {
|
||||
return std::string(fmt);
|
||||
} else {
|
||||
std::string result;
|
||||
detail::format_impl(result, fmt, std::forward<Args>(args)...);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 便捷宏 - 自动获取日志服务
|
||||
#define E2D_LOG(level, ...) \
|
||||
do { \
|
||||
if (auto logService = ::extra2d::ServiceLocator::instance().tryGetService<::extra2d::ILogger>()) { \
|
||||
if (logService->isEnabled(level)) { \
|
||||
logService->log(level, ::extra2d::format_str(__VA_ARGS__)); \
|
||||
} \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define E2D_LOG_TRACE(...) \
|
||||
E2D_LOG(::extra2d::LogLevel::Trace, __VA_ARGS__)
|
||||
|
||||
#define E2D_LOG_DEBUG(...) \
|
||||
E2D_LOG(::extra2d::LogLevel::Debug, __VA_ARGS__)
|
||||
|
||||
#define E2D_LOG_INFO(...) \
|
||||
E2D_LOG(::extra2d::LogLevel::Info, __VA_ARGS__)
|
||||
|
||||
#define E2D_LOG_WARN(...) \
|
||||
E2D_LOG(::extra2d::LogLevel::Warn, __VA_ARGS__)
|
||||
|
||||
#define E2D_LOG_ERROR(...) \
|
||||
E2D_LOG(::extra2d::LogLevel::Error, __VA_ARGS__)
|
||||
|
||||
#define E2D_LOG_FATAL(...) \
|
||||
E2D_LOG(::extra2d::LogLevel::Fatal, __VA_ARGS__)
|
||||
|
||||
// 简写宏
|
||||
#define E2D_INFO(...) E2D_LOG_INFO(__VA_ARGS__)
|
||||
#define E2D_WARN(...) E2D_LOG_WARN(__VA_ARGS__)
|
||||
#define E2D_ERROR(...) E2D_LOG_ERROR(__VA_ARGS__)
|
||||
#define E2D_FATAL(...) E2D_LOG_FATAL(__VA_ARGS__)
|
||||
|
||||
#ifdef E2D_DEBUG
|
||||
#define E2D_DEBUG_LOG(...) E2D_LOG_DEBUG(__VA_ARGS__)
|
||||
#define E2D_TRACE(...) E2D_LOG_TRACE(__VA_ARGS__)
|
||||
#else
|
||||
#define E2D_DEBUG_LOG(...)
|
||||
#define E2D_TRACE(...)
|
||||
#endif
|
||||
|
|
@ -1,202 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
#include <extra2d/core/export.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
/**
|
||||
* @file logger.h
|
||||
* @brief 日志工具头文件
|
||||
*
|
||||
* 提供便捷的日志宏定义,实际实现位于 logger_service.h
|
||||
*/
|
||||
|
||||
namespace extra2d {
|
||||
#include <extra2d/services/logger_service.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
|
||||
enum class LogLevel {
|
||||
Trace = 0,
|
||||
Debug = 1,
|
||||
Info = 2,
|
||||
Warn = 3,
|
||||
Error = 4,
|
||||
Fatal = 5,
|
||||
Off = 6
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T> inline std::string to_string_arg(const T &value) {
|
||||
if constexpr (std::is_same_v<T, std::string>) {
|
||||
return value;
|
||||
} else if constexpr (std::is_same_v<T, const char *> ||
|
||||
std::is_same_v<T, char *>) {
|
||||
return value ? std::string(value) : std::string("(null)");
|
||||
} else if constexpr (std::is_same_v<T, bool>) {
|
||||
return value ? "true" : "false";
|
||||
} else if constexpr (std::is_arithmetic_v<T>) {
|
||||
if constexpr (std::is_floating_point_v<T>) {
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "%.2f", static_cast<double>(value));
|
||||
return buf;
|
||||
} else {
|
||||
return std::to_string(value);
|
||||
}
|
||||
} else {
|
||||
std::ostringstream oss;
|
||||
oss << value;
|
||||
return oss.str();
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string format_impl(const char *fmt) {
|
||||
std::string result;
|
||||
while (*fmt) {
|
||||
if (*fmt == '{' && *(fmt + 1) == '}') {
|
||||
result += "{}";
|
||||
fmt += 2;
|
||||
} else {
|
||||
result += *fmt;
|
||||
++fmt;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
inline std::string format_impl(const char *fmt, const T &first,
|
||||
const Args &...rest) {
|
||||
std::string result;
|
||||
while (*fmt) {
|
||||
if (*fmt == '{') {
|
||||
if (*(fmt + 1) == '}') {
|
||||
result += to_string_arg(first);
|
||||
fmt += 2;
|
||||
result += format_impl(fmt, rest...);
|
||||
return result;
|
||||
} else if (*(fmt + 1) == ':') {
|
||||
const char *end = fmt + 2;
|
||||
while (*end && *end != '}')
|
||||
++end;
|
||||
if (*end == '}') {
|
||||
std::string spec(fmt + 2, end);
|
||||
if (spec.find('x') != std::string::npos ||
|
||||
spec.find('X') != std::string::npos) {
|
||||
if constexpr (std::is_integral_v<T>) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "0x%x",
|
||||
static_cast<unsigned int>(first));
|
||||
result += buf;
|
||||
} else {
|
||||
result += to_string_arg(first);
|
||||
}
|
||||
} else if (spec.find('f') != std::string::npos ||
|
||||
spec.find('.') != std::string::npos) {
|
||||
if constexpr (std::is_arithmetic_v<T>) {
|
||||
int precision = 2;
|
||||
auto dot = spec.find('.');
|
||||
if (dot != std::string::npos) {
|
||||
precision = 0;
|
||||
for (size_t i = dot + 1;
|
||||
i < spec.size() && spec[i] >= '0' && spec[i] <= '9'; ++i) {
|
||||
precision = precision * 10 + (spec[i] - '0');
|
||||
}
|
||||
}
|
||||
char fmtbuf[16];
|
||||
snprintf(fmtbuf, sizeof(fmtbuf), "%%.%df", precision);
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), fmtbuf, static_cast<double>(first));
|
||||
result += buf;
|
||||
} else {
|
||||
result += to_string_arg(first);
|
||||
}
|
||||
} else {
|
||||
result += to_string_arg(first);
|
||||
}
|
||||
fmt = end + 1;
|
||||
result += format_impl(fmt, rest...);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
result += *fmt;
|
||||
++fmt;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename... Args>
|
||||
inline std::string e2d_format(const char *fmt, const Args &...args) {
|
||||
return detail::format_impl(fmt, args...);
|
||||
}
|
||||
|
||||
inline std::string e2d_format(const char *fmt) { return std::string(fmt); }
|
||||
|
||||
class E2D_API Logger {
|
||||
public:
|
||||
static void init();
|
||||
static void shutdown();
|
||||
|
||||
static void setLevel(LogLevel level);
|
||||
static void setConsoleOutput(bool enable);
|
||||
static void setFileOutput(const std::string &filename);
|
||||
|
||||
static LogLevel getLevel() { return level_; }
|
||||
|
||||
template <typename... Args>
|
||||
static void log(LogLevel level, const char *fmt, const Args &...args) {
|
||||
if (static_cast<int>(level) < static_cast<int>(level_))
|
||||
return;
|
||||
std::string msg = e2d_format(fmt, args...);
|
||||
outputLog(level, msg.c_str());
|
||||
}
|
||||
|
||||
static void log(LogLevel level, const char *msg) {
|
||||
if (static_cast<int>(level) < static_cast<int>(level_))
|
||||
return;
|
||||
outputLog(level, msg);
|
||||
}
|
||||
|
||||
private:
|
||||
static void outputLog(LogLevel level, const char *msg);
|
||||
static const char *getLevelString(LogLevel level);
|
||||
static void writeToConsole(LogLevel level, const char *msg);
|
||||
static void writeToFile(LogLevel level, const char *msg);
|
||||
|
||||
static LogLevel level_;
|
||||
static bool initialized_;
|
||||
static bool consoleOutput_;
|
||||
static bool fileOutput_;
|
||||
static std::string logFile_;
|
||||
static void *logFileHandle_;
|
||||
};
|
||||
|
||||
#ifdef E2D_DEBUG
|
||||
#define E2D_LOG_TRACE(...) \
|
||||
::extra2d::Logger::log(::extra2d::LogLevel::Trace, __VA_ARGS__)
|
||||
#define E2D_LOG_DEBUG(...) \
|
||||
::extra2d::Logger::log(::extra2d::LogLevel::Debug, __VA_ARGS__)
|
||||
#else
|
||||
#define E2D_LOG_TRACE(...)
|
||||
#define E2D_LOG_DEBUG(...)
|
||||
#endif
|
||||
|
||||
#define E2D_LOG_INFO(...) \
|
||||
::extra2d::Logger::log(::extra2d::LogLevel::Info, __VA_ARGS__)
|
||||
#define E2D_LOG_WARN(...) \
|
||||
::extra2d::Logger::log(::extra2d::LogLevel::Warn, __VA_ARGS__)
|
||||
#define E2D_LOG_ERROR(...) \
|
||||
::extra2d::Logger::log(::extra2d::LogLevel::Error, __VA_ARGS__)
|
||||
#define E2D_LOG_FATAL(...) \
|
||||
::extra2d::Logger::log(::extra2d::LogLevel::Fatal, __VA_ARGS__)
|
||||
|
||||
#define E2D_INFO(...) E2D_LOG_INFO(__VA_ARGS__)
|
||||
#define E2D_WARN(...) E2D_LOG_WARN(__VA_ARGS__)
|
||||
#define E2D_ERROR(...) E2D_LOG_ERROR(__VA_ARGS__)
|
||||
#define E2D_FATAL(...) E2D_LOG_FATAL(__VA_ARGS__)
|
||||
#ifdef E2D_DEBUG
|
||||
#define E2D_DEBUG_LOG(...) E2D_LOG_DEBUG(__VA_ARGS__)
|
||||
#define E2D_TRACE(...) E2D_LOG_TRACE(__VA_ARGS__)
|
||||
#else
|
||||
#define E2D_DEBUG_LOG(...)
|
||||
#define E2D_TRACE(...)
|
||||
#endif
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,366 +1,268 @@
|
|||
#include <extra2d/app/application.h>
|
||||
#include <extra2d/core/module_meta.h>
|
||||
#include <extra2d/graphics/vram_manager.h>
|
||||
#include <extra2d/modules/config_module.h>
|
||||
#include <extra2d/modules/input_module.h>
|
||||
#include <extra2d/modules/render_module.h>
|
||||
#include <extra2d/modules/window_module.h>
|
||||
#include <extra2d/services/camera_service.h>
|
||||
#include <extra2d/services/event_service.h>
|
||||
#include <extra2d/core/registry.h>
|
||||
#include <extra2d/platform/window_module.h>
|
||||
#include <extra2d/platform/input_module.h>
|
||||
#include <extra2d/graphics/core/render_module.h>
|
||||
#include <extra2d/platform/iwindow.h>
|
||||
#include <extra2d/platform/iinput.h>
|
||||
#include <extra2d/graphics/core/render_backend.h>
|
||||
#include <extra2d/services/scene_service.h>
|
||||
#include <extra2d/services/timer_service.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <extra2d/services/event_service.h>
|
||||
#include <extra2d/services/camera_service.h>
|
||||
#include <extra2d/services/logger_service.h>
|
||||
#include <extra2d/graphics/memory/vram_manager.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
static double getTimeSeconds() {
|
||||
#ifdef __SWITCH__
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return static_cast<double>(ts.tv_sec) +
|
||||
static_cast<double>(ts.tv_nsec) / 1000000000.0;
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return static_cast<double>(ts.tv_sec) +
|
||||
static_cast<double>(ts.tv_nsec) / 1000000000.0;
|
||||
#else
|
||||
using namespace std::chrono;
|
||||
auto now = steady_clock::now();
|
||||
auto duration = now.time_since_epoch();
|
||||
return duration_cast<std::chrono::duration<double>>(duration).count();
|
||||
using namespace std::chrono;
|
||||
auto now = steady_clock::now();
|
||||
auto duration = now.time_since_epoch();
|
||||
return duration_cast<std::chrono::duration<double>>(duration).count();
|
||||
#endif
|
||||
}
|
||||
|
||||
Application &Application::get() {
|
||||
static Application instance;
|
||||
return instance;
|
||||
Application& Application::get() {
|
||||
static Application instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool Application::init() {
|
||||
AppConfig cfg;
|
||||
return init(cfg);
|
||||
}
|
||||
|
||||
bool Application::init(const AppConfig &config) {
|
||||
if (initialized_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 先注册核心服务,因为模块初始化时可能需要它们
|
||||
registerCoreServices();
|
||||
|
||||
if (!initModules(config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto windowModule = getModule<WindowModule>();
|
||||
if (!windowModule || !windowModule->isInitialized()) {
|
||||
E2D_LOG_ERROR("Window module not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
window_ = windowModule->getWindow();
|
||||
if (!window_) {
|
||||
E2D_LOG_ERROR("Window not created");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto inputModule = getModule<InputModule>();
|
||||
if (inputModule) {
|
||||
inputModule->setWindow(window_);
|
||||
}
|
||||
|
||||
auto renderModule = getModule<RenderModule>();
|
||||
if (renderModule) {
|
||||
renderModule->setWindow(window_);
|
||||
}
|
||||
|
||||
// 注册 CameraService(需要 window)
|
||||
registerCameraService();
|
||||
|
||||
if (!ServiceLocator::instance().initializeAll()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
|
||||
if (cameraService && window_) {
|
||||
window_->onResize([cameraService](int width, int height) {
|
||||
cameraService->updateViewport(width, height);
|
||||
cameraService->applyViewportAdapter();
|
||||
|
||||
auto sceneService =
|
||||
ServiceLocator::instance().getService<ISceneService>();
|
||||
if (sceneService) {
|
||||
auto currentScene = sceneService->getCurrentScene();
|
||||
if (currentScene) {
|
||||
currentScene->setViewportSize(static_cast<float>(width),
|
||||
static_cast<float>(height));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
running_ = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Application::init(const std::string &configPath) {
|
||||
if (initialized_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto configModule = getModule<ConfigModule>();
|
||||
if (configModule) {
|
||||
configModule->setConfigPath(configPath);
|
||||
}
|
||||
|
||||
AppConfig cfg;
|
||||
return init(cfg);
|
||||
}
|
||||
|
||||
bool Application::initModules(const AppConfig &config) {
|
||||
auto configModule = getModule<ConfigModule>();
|
||||
if (configModule) {
|
||||
configModule->setAppConfig(config);
|
||||
}
|
||||
|
||||
if (!ModuleRegistry::instance().createAndInitAll()) {
|
||||
E2D_LOG_ERROR("Failed to initialize modules");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::registerCoreServices() {
|
||||
auto &locator = ServiceLocator::instance();
|
||||
|
||||
if (!locator.hasService<IEventService>()) {
|
||||
locator.registerService<IEventService>(makeShared<EventService>());
|
||||
}
|
||||
|
||||
if (!locator.hasService<ISceneService>()) {
|
||||
locator.registerService<ISceneService>(makeShared<SceneService>());
|
||||
}
|
||||
|
||||
if (!locator.hasService<ITimerService>()) {
|
||||
locator.registerService<ITimerService>(makeShared<TimerService>());
|
||||
}
|
||||
}
|
||||
|
||||
void Application::registerCameraService() {
|
||||
auto &locator = ServiceLocator::instance();
|
||||
|
||||
if (!locator.hasService<ICameraService>()) {
|
||||
auto cameraService = makeShared<CameraService>();
|
||||
if (window_) {
|
||||
cameraService->setViewport(0, static_cast<float>(window_->width()),
|
||||
static_cast<float>(window_->height()), 0);
|
||||
ViewportConfig vpConfig;
|
||||
vpConfig.logicWidth = static_cast<float>(window_->width());
|
||||
vpConfig.logicHeight = static_cast<float>(window_->height());
|
||||
vpConfig.mode = ViewportMode::AspectRatio;
|
||||
cameraService->setViewportConfig(vpConfig);
|
||||
cameraService->updateViewport(window_->width(), window_->height());
|
||||
}
|
||||
locator.registerService<ICameraService>(cameraService);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::shutdown() {
|
||||
if (!initialized_)
|
||||
return;
|
||||
|
||||
E2D_LOG_INFO("Application shutting down...");
|
||||
|
||||
VRAMMgr::get().printStats();
|
||||
|
||||
// 先销毁模块(它们可能需要服务和窗口)
|
||||
ModuleRegistry::instance().destroyAll();
|
||||
|
||||
// 最后清除服务
|
||||
ServiceLocator::instance().clear();
|
||||
|
||||
initialized_ = false;
|
||||
running_ = false;
|
||||
|
||||
E2D_LOG_INFO("Application shutdown complete");
|
||||
Application::Application() {
|
||||
Registry::instance().setApp(this);
|
||||
}
|
||||
|
||||
Application::~Application() {
|
||||
if (initialized_) {
|
||||
shutdown();
|
||||
}
|
||||
if (initialized_) {
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
bool Application::init() {
|
||||
AppConfig cfg;
|
||||
return init(cfg);
|
||||
}
|
||||
|
||||
bool Application::init(const AppConfig& config) {
|
||||
if (initialized_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
appConfig_ = config;
|
||||
|
||||
// 首先注册日志服务(模块初始化可能需要它)
|
||||
auto& locator = ServiceLocator::instance();
|
||||
if (!locator.hasService<ILogger>()) {
|
||||
auto logger = makeShared<ConsoleLogger>();
|
||||
locator.registerService(std::static_pointer_cast<ILogger>(logger));
|
||||
}
|
||||
|
||||
// 初始化所有模块(拓扑排序)
|
||||
if (!Registry::instance().init()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 模块初始化完成后,注册其他核心服务
|
||||
registerCoreServices();
|
||||
|
||||
initialized_ = true;
|
||||
running_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::registerCoreServices() {
|
||||
auto& locator = ServiceLocator::instance();
|
||||
|
||||
if (!locator.hasService<ISceneService>()) {
|
||||
auto service = makeShared<SceneService>();
|
||||
locator.registerService(std::static_pointer_cast<ISceneService>(service));
|
||||
}
|
||||
|
||||
if (!locator.hasService<ITimerService>()) {
|
||||
auto service = makeShared<TimerService>();
|
||||
locator.registerService(std::static_pointer_cast<ITimerService>(service));
|
||||
}
|
||||
|
||||
if (!locator.hasService<IEventService>()) {
|
||||
auto service = makeShared<EventService>();
|
||||
locator.registerService(std::static_pointer_cast<IEventService>(service));
|
||||
}
|
||||
|
||||
auto* winMod = get<WindowModule>();
|
||||
if (winMod && winMod->win() && !locator.hasService<ICameraService>()) {
|
||||
auto cameraService = makeShared<CameraService>();
|
||||
auto* win = winMod->win();
|
||||
cameraService->setViewport(0, static_cast<float>(win->width()),
|
||||
static_cast<float>(win->height()), 0);
|
||||
ViewportConfig vpConfig;
|
||||
vpConfig.logicWidth = static_cast<float>(win->width());
|
||||
vpConfig.logicHeight = static_cast<float>(win->height());
|
||||
vpConfig.mode = ViewportMode::AspectRatio;
|
||||
cameraService->setViewportConfig(vpConfig);
|
||||
cameraService->updateViewport(win->width(), win->height());
|
||||
locator.registerService(std::static_pointer_cast<ICameraService>(cameraService));
|
||||
|
||||
win->onResize([cameraService](int width, int height) {
|
||||
cameraService->updateViewport(width, height);
|
||||
cameraService->applyViewportAdapter();
|
||||
});
|
||||
}
|
||||
|
||||
locator.initializeAll();
|
||||
}
|
||||
|
||||
void Application::shutdown() {
|
||||
if (!initialized_) return;
|
||||
|
||||
VRAMMgr::get().printStats();
|
||||
|
||||
ServiceLocator::instance().clear();
|
||||
Registry::instance().shutdown();
|
||||
Registry::instance().clear();
|
||||
|
||||
initialized_ = false;
|
||||
running_ = false;
|
||||
}
|
||||
|
||||
void Application::run() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastFrameTime_ = getTimeSeconds();
|
||||
|
||||
while (running_ && !window_->shouldClose()) {
|
||||
mainLoop();
|
||||
}
|
||||
|
||||
// 主循环结束后自动清理
|
||||
shutdown();
|
||||
if (!initialized_) return;
|
||||
|
||||
auto* winMod = get<WindowModule>();
|
||||
if (!winMod || !winMod->win()) return;
|
||||
|
||||
lastFrameTime_ = getTimeSeconds();
|
||||
|
||||
while (running_ && !winMod->win()->shouldClose()) {
|
||||
mainLoop();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::quit() {
|
||||
shouldQuit_ = true;
|
||||
running_ = false;
|
||||
shouldQuit_ = true;
|
||||
running_ = false;
|
||||
}
|
||||
|
||||
void Application::pause() {
|
||||
if (!paused_) {
|
||||
paused_ = true;
|
||||
ServiceLocator::instance().pauseAll();
|
||||
}
|
||||
if (!paused_) {
|
||||
paused_ = true;
|
||||
ServiceLocator::instance().pauseAll();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::resume() {
|
||||
if (paused_) {
|
||||
paused_ = false;
|
||||
ServiceLocator::instance().resumeAll();
|
||||
lastFrameTime_ = getTimeSeconds();
|
||||
}
|
||||
if (paused_) {
|
||||
paused_ = false;
|
||||
ServiceLocator::instance().resumeAll();
|
||||
lastFrameTime_ = getTimeSeconds();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::mainLoop() {
|
||||
double currentTime = getTimeSeconds();
|
||||
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
|
||||
lastFrameTime_ = currentTime;
|
||||
|
||||
totalTime_ += deltaTime_;
|
||||
|
||||
frameCount_++;
|
||||
fpsTimer_ += deltaTime_;
|
||||
if (fpsTimer_ >= 1.0f) {
|
||||
currentFps_ = frameCount_;
|
||||
frameCount_ = 0;
|
||||
fpsTimer_ -= 1.0f;
|
||||
}
|
||||
|
||||
window_->poll();
|
||||
|
||||
auto eventService = ServiceLocator::instance().getService<IEventService>();
|
||||
if (eventService) {
|
||||
eventService->processQueue();
|
||||
}
|
||||
|
||||
if (!paused_) {
|
||||
update();
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
ConfigManager::instance().update(deltaTime_);
|
||||
double currentTime = getTimeSeconds();
|
||||
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
|
||||
lastFrameTime_ = currentTime;
|
||||
|
||||
totalTime_ += deltaTime_;
|
||||
|
||||
frameCount_++;
|
||||
fpsTimer_ += deltaTime_;
|
||||
if (fpsTimer_ >= 1.0f) {
|
||||
currentFps_ = frameCount_;
|
||||
frameCount_ = 0;
|
||||
fpsTimer_ -= 1.0f;
|
||||
}
|
||||
|
||||
auto* winMod = get<WindowModule>();
|
||||
if (winMod && winMod->win()) {
|
||||
winMod->win()->poll();
|
||||
}
|
||||
|
||||
auto eventService = ServiceLocator::instance().getService<IEventService>();
|
||||
if (eventService) {
|
||||
eventService->processQueue();
|
||||
}
|
||||
|
||||
if (!paused_) {
|
||||
update();
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
// 帧率限制
|
||||
auto* renderMod = get<RenderModule>();
|
||||
if (renderMod && renderMod->renderer()) {
|
||||
// 这里可以添加帧率限制逻辑
|
||||
}
|
||||
}
|
||||
|
||||
void Application::update() {
|
||||
ServiceLocator::instance().updateAll(deltaTime_);
|
||||
|
||||
auto modules = ModuleRegistry::instance().getAllModules();
|
||||
auto ctx = UpdateContext(modules, deltaTime_);
|
||||
if (!modules.empty()) {
|
||||
ctx.next();
|
||||
}
|
||||
ServiceLocator::instance().updateAll(deltaTime_);
|
||||
|
||||
auto* inputMod = get<InputModule>();
|
||||
if (inputMod) {
|
||||
inputMod->update();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::render() {
|
||||
RenderBackend *renderer = nullptr;
|
||||
auto renderModule = getModule<RenderModule>();
|
||||
if (renderModule) {
|
||||
renderer = renderModule->getRenderer();
|
||||
}
|
||||
|
||||
if (!renderer) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
|
||||
if (cameraService) {
|
||||
const auto &vp = cameraService->getViewportResult().viewport;
|
||||
renderer->setViewport(
|
||||
static_cast<int>(vp.origin.x), static_cast<int>(vp.origin.y),
|
||||
static_cast<int>(vp.size.width), static_cast<int>(vp.size.height));
|
||||
|
||||
renderer->setViewProjection(cameraService->getViewProjectionMatrix());
|
||||
} else {
|
||||
renderer->setViewport(0, 0, window_->width(), window_->height());
|
||||
}
|
||||
|
||||
auto modules = ModuleRegistry::instance().getAllModules();
|
||||
|
||||
{
|
||||
auto ctx = RenderContext(modules, RenderContext::Phase::Before);
|
||||
if (!modules.empty()) {
|
||||
ctx.next();
|
||||
auto* renderMod = get<RenderModule>();
|
||||
if (!renderMod || !renderMod->renderer()) return;
|
||||
|
||||
auto* renderer = renderMod->renderer();
|
||||
auto* winMod = get<WindowModule>();
|
||||
if (!winMod || !winMod->win()) return;
|
||||
|
||||
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
|
||||
if (cameraService) {
|
||||
const auto& vp = cameraService->getViewportResult().viewport;
|
||||
renderer->setViewport(
|
||||
static_cast<int>(vp.origin.x), static_cast<int>(vp.origin.y),
|
||||
static_cast<int>(vp.size.width), static_cast<int>(vp.size.height));
|
||||
renderer->setViewProjection(cameraService->getViewProjectionMatrix());
|
||||
} else {
|
||||
renderer->setViewport(0, 0, winMod->win()->width(), winMod->win()->height());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto ctx = RenderContext(modules, RenderContext::Phase::On);
|
||||
if (!modules.empty()) {
|
||||
ctx.next();
|
||||
|
||||
auto sceneService = ServiceLocator::instance().getService<ISceneService>();
|
||||
if (sceneService) {
|
||||
sceneService->render(*renderer);
|
||||
}
|
||||
}
|
||||
|
||||
auto sceneService = ServiceLocator::instance().getService<ISceneService>();
|
||||
if (sceneService) {
|
||||
sceneService->render(*renderer);
|
||||
}
|
||||
|
||||
{
|
||||
auto ctx = RenderContext(modules, RenderContext::Phase::After);
|
||||
if (!modules.empty()) {
|
||||
ctx.next();
|
||||
}
|
||||
}
|
||||
|
||||
winMod->win()->swap();
|
||||
}
|
||||
|
||||
IInput &Application::input() { return *window_->input(); }
|
||||
|
||||
RenderBackend &Application::renderer() {
|
||||
auto renderModule = getModule<RenderModule>();
|
||||
if (renderModule && renderModule->getRenderer()) {
|
||||
return *renderModule->getRenderer();
|
||||
}
|
||||
E2D_LOG_ERROR("RenderModule not initialized - renderer() called too early");
|
||||
std::abort();
|
||||
IWindow* Application::window() {
|
||||
auto* winMod = get<WindowModule>();
|
||||
return winMod ? winMod->win() : nullptr;
|
||||
}
|
||||
|
||||
SharedPtr<ISceneService> Application::scenes() {
|
||||
return ServiceLocator::instance().getService<ISceneService>();
|
||||
RenderBackend* Application::renderer() {
|
||||
auto* renderMod = get<RenderModule>();
|
||||
return renderMod ? renderMod->renderer() : nullptr;
|
||||
}
|
||||
|
||||
SharedPtr<ITimerService> Application::timers() {
|
||||
return ServiceLocator::instance().getService<ITimerService>();
|
||||
}
|
||||
|
||||
SharedPtr<IEventService> Application::events() {
|
||||
return ServiceLocator::instance().getService<IEventService>();
|
||||
}
|
||||
|
||||
SharedPtr<ICameraService> Application::camera() {
|
||||
return ServiceLocator::instance().getService<ICameraService>();
|
||||
IInput* Application::input() {
|
||||
auto* winMod = get<WindowModule>();
|
||||
return (winMod && winMod->win()) ? winMod->win()->input() : nullptr;
|
||||
}
|
||||
|
||||
void Application::enterScene(Ptr<Scene> scene) {
|
||||
auto sceneService = ServiceLocator::instance().getService<ISceneService>();
|
||||
if (sceneService && scene) {
|
||||
scene->setViewportSize(static_cast<float>(window_->width()),
|
||||
static_cast<float>(window_->height()));
|
||||
sceneService->enterScene(scene);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigManager &Application::config() { return ConfigManager::instance(); }
|
||||
|
||||
const AppConfig &Application::getConfig() const {
|
||||
return ConfigManager::instance().appConfig();
|
||||
auto sceneService = ServiceLocator::instance().getService<ISceneService>();
|
||||
auto* winMod = get<WindowModule>();
|
||||
if (sceneService && scene && winMod && winMod->win()) {
|
||||
scene->setViewportSize(static_cast<float>(winMod->win()->width()),
|
||||
static_cast<float>(winMod->win()->height()));
|
||||
sceneService->enterScene(scene);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,289 +0,0 @@
|
|||
#include <extra2d/config/config_loader.h>
|
||||
#include <extra2d/config/config_manager.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
static std::string trim(const std::string &str) {
|
||||
size_t start = 0;
|
||||
while (start < str.length() &&
|
||||
std::isspace(static_cast<unsigned char>(str[start]))) {
|
||||
++start;
|
||||
}
|
||||
size_t end = str.length();
|
||||
while (end > start &&
|
||||
std::isspace(static_cast<unsigned char>(str[end - 1]))) {
|
||||
--end;
|
||||
}
|
||||
return str.substr(start, end - start);
|
||||
}
|
||||
|
||||
static std::string toLower(const std::string &str) {
|
||||
std::string result = str;
|
||||
for (char &c : result) {
|
||||
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
using IniData = std::map<std::string, std::map<std::string, std::string>>;
|
||||
|
||||
static ConfigLoadResult parseIniContent(const std::string &content,
|
||||
IniData &data) {
|
||||
std::istringstream stream(content);
|
||||
std::string line;
|
||||
std::string currentSection;
|
||||
int lineNumber = 0;
|
||||
|
||||
while (std::getline(stream, line)) {
|
||||
++lineNumber;
|
||||
line = trim(line);
|
||||
|
||||
if (line.empty() || line[0] == ';' || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line[0] == '[') {
|
||||
size_t endBracket = line.find(']');
|
||||
if (endBracket == std::string::npos) {
|
||||
return ConfigLoadResult::error("INI 解析错误: 缺少右括号", lineNumber);
|
||||
}
|
||||
currentSection = trim(line.substr(1, endBracket - 1));
|
||||
if (data.find(currentSection) == data.end()) {
|
||||
data[currentSection] = std::map<std::string, std::string>();
|
||||
}
|
||||
} else {
|
||||
size_t equalPos = line.find('=');
|
||||
if (equalPos == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string key = trim(line.substr(0, equalPos));
|
||||
std::string value = trim(line.substr(equalPos + 1));
|
||||
|
||||
if (currentSection.empty()) {
|
||||
return ConfigLoadResult::error("INI 解析错误: 键值对不在任何节中",
|
||||
lineNumber);
|
||||
}
|
||||
|
||||
data[currentSection][key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return ConfigLoadResult::ok();
|
||||
}
|
||||
|
||||
static std::string getIniValue(const IniData &data, const std::string §ion,
|
||||
const std::string &key,
|
||||
const std::string &defaultValue = "") {
|
||||
auto sectionIt = data.find(section);
|
||||
if (sectionIt == data.end()) {
|
||||
return defaultValue;
|
||||
}
|
||||
auto keyIt = sectionIt->second.find(key);
|
||||
if (keyIt == sectionIt->second.end()) {
|
||||
return defaultValue;
|
||||
}
|
||||
return keyIt->second;
|
||||
}
|
||||
|
||||
static bool hasIniValue(const IniData &data, const std::string §ion,
|
||||
const std::string &key) {
|
||||
auto sectionIt = data.find(section);
|
||||
if (sectionIt == data.end()) {
|
||||
return false;
|
||||
}
|
||||
return sectionIt->second.find(key) != sectionIt->second.end();
|
||||
}
|
||||
|
||||
ConfigLoadResult IniConfigLoader::load(const std::string &filepath,
|
||||
AppConfig &config) {
|
||||
E2D_LOG_INFO("正在从 INI 文件加载应用配置: {}", filepath);
|
||||
|
||||
std::ifstream file(filepath);
|
||||
if (!file.is_open()) {
|
||||
E2D_LOG_ERROR("无法打开配置文件: {}", filepath);
|
||||
return ConfigLoadResult::error("无法打开配置文件: " + filepath);
|
||||
}
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
std::string content = buffer.str();
|
||||
file.close();
|
||||
|
||||
return loadFromString(content, config);
|
||||
}
|
||||
|
||||
ConfigSaveResult IniConfigLoader::save(const std::string &filepath,
|
||||
const AppConfig &config) {
|
||||
E2D_LOG_INFO("正在保存应用配置到 INI 文件: {}", filepath);
|
||||
|
||||
std::string content = saveToString(config);
|
||||
|
||||
std::ofstream file(filepath);
|
||||
if (!file.is_open()) {
|
||||
E2D_LOG_ERROR("无法创建配置文件: {}", filepath);
|
||||
return ConfigSaveResult::error("无法创建配置文件: " + filepath);
|
||||
}
|
||||
|
||||
file << content;
|
||||
file.close();
|
||||
|
||||
E2D_LOG_INFO("配置已成功保存到: {}", filepath);
|
||||
return ConfigSaveResult::ok();
|
||||
}
|
||||
|
||||
ConfigLoadResult IniConfigLoader::loadFromString(const std::string &content,
|
||||
AppConfig &config) {
|
||||
IniData data;
|
||||
auto result = parseIniContent(content, data);
|
||||
if (result.hasError()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (hasIniValue(data, "app", "name")) {
|
||||
config.appName = getIniValue(data, "app", "name");
|
||||
}
|
||||
if (hasIniValue(data, "app", "version")) {
|
||||
config.appVersion = getIniValue(data, "app", "version");
|
||||
}
|
||||
if (hasIniValue(data, "app", "organization")) {
|
||||
config.organization = getIniValue(data, "app", "organization");
|
||||
}
|
||||
if (hasIniValue(data, "app", "configFile")) {
|
||||
config.configFile = getIniValue(data, "app", "configFile");
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("INI 应用配置加载成功");
|
||||
return ConfigLoadResult::ok();
|
||||
}
|
||||
|
||||
std::string IniConfigLoader::saveToString(const AppConfig &config) {
|
||||
std::ostringstream oss;
|
||||
|
||||
oss << "[app]\n";
|
||||
oss << "name=" << config.appName << "\n";
|
||||
oss << "version=" << config.appVersion << "\n";
|
||||
oss << "organization=" << config.organization << "\n";
|
||||
oss << "configFile=" << config.configFile << "\n";
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
ConfigLoadResult IniConfigLoader::loadWithModules(const std::string &filepath) {
|
||||
E2D_LOG_INFO("正在从 INI 文件加载完整配置: {}", filepath);
|
||||
|
||||
std::ifstream file(filepath);
|
||||
if (!file.is_open()) {
|
||||
E2D_LOG_ERROR("无法打开配置文件: {}", filepath);
|
||||
return ConfigLoadResult::error("无法打开配置文件: " + filepath);
|
||||
}
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
std::string content = buffer.str();
|
||||
file.close();
|
||||
|
||||
IniData data;
|
||||
auto result = parseIniContent(content, data);
|
||||
if (result.hasError()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("完整配置加载成功");
|
||||
return ConfigLoadResult::ok();
|
||||
}
|
||||
|
||||
ConfigSaveResult IniConfigLoader::saveWithModules(const std::string &filepath) {
|
||||
E2D_LOG_INFO("正在保存完整配置到 INI 文件: {}", filepath);
|
||||
|
||||
std::ostringstream oss;
|
||||
|
||||
oss << saveToString(ConfigManager::instance().appConfig());
|
||||
|
||||
std::ofstream file(filepath);
|
||||
if (!file.is_open()) {
|
||||
E2D_LOG_ERROR("无法创建配置文件: {}", filepath);
|
||||
return ConfigSaveResult::error("无法创建配置文件: " + filepath);
|
||||
}
|
||||
|
||||
file << oss.str();
|
||||
file.close();
|
||||
|
||||
E2D_LOG_INFO("完整配置已成功保存到: {}", filepath);
|
||||
return ConfigSaveResult::ok();
|
||||
}
|
||||
|
||||
bool IniConfigLoader::supportsFile(const std::string &filepath) const {
|
||||
if (filepath.length() >= 4) {
|
||||
std::string ext = filepath.substr(filepath.length() - 4);
|
||||
for (char &c : ext)
|
||||
c = static_cast<char>(std::tolower(c));
|
||||
return ext == ".ini";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
UniquePtr<ConfigLoader> IniConfigLoader::clone() const {
|
||||
return makeUnique<IniConfigLoader>();
|
||||
}
|
||||
|
||||
std::string IniConfigLoader::sectionKey(const std::string §ion,
|
||||
const std::string &key) const {
|
||||
return section + "." + key;
|
||||
}
|
||||
|
||||
ConfigLoadResult IniConfigLoader::parseInt(const std::string &value,
|
||||
int &result,
|
||||
const std::string &fieldName) {
|
||||
try {
|
||||
size_t pos;
|
||||
result = std::stoi(value, &pos);
|
||||
if (pos != value.length()) {
|
||||
return ConfigLoadResult::error("无法解析整数值: " + value, -1, fieldName);
|
||||
}
|
||||
return ConfigLoadResult::ok();
|
||||
} catch (const std::exception &e) {
|
||||
return ConfigLoadResult::error(std::string("解析整数失败: ") + e.what(), -1,
|
||||
fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigLoadResult IniConfigLoader::parseFloat(const std::string &value,
|
||||
float &result,
|
||||
const std::string &fieldName) {
|
||||
try {
|
||||
size_t pos;
|
||||
result = std::stof(value, &pos);
|
||||
if (pos != value.length()) {
|
||||
return ConfigLoadResult::error("无法解析浮点数值: " + value, -1,
|
||||
fieldName);
|
||||
}
|
||||
return ConfigLoadResult::ok();
|
||||
} catch (const std::exception &e) {
|
||||
return ConfigLoadResult::error(std::string("解析浮点数失败: ") + e.what(),
|
||||
-1, fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigLoadResult IniConfigLoader::parseBool(const std::string &value,
|
||||
bool &result,
|
||||
const std::string &fieldName) {
|
||||
std::string lower = toLower(value);
|
||||
if (lower == "true" || lower == "1" || lower == "yes" || lower == "on") {
|
||||
result = true;
|
||||
return ConfigLoadResult::ok();
|
||||
}
|
||||
if (lower == "false" || lower == "0" || lower == "no" || lower == "off") {
|
||||
result = false;
|
||||
return ConfigLoadResult::ok();
|
||||
}
|
||||
return ConfigLoadResult::error("无法解析布尔值: " + value, -1, fieldName);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
#include <extra2d/config/config_loader.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
ConfigLoadResult JsonConfigLoader::load(const std::string& filepath, AppConfig& config) {
|
||||
E2D_LOG_INFO("正在从 JSON 文件加载应用配置: {}", filepath);
|
||||
|
||||
std::ifstream file(filepath);
|
||||
if (!file.is_open()) {
|
||||
E2D_LOG_ERROR("无法打开配置文件: {}", filepath);
|
||||
return ConfigLoadResult::error("无法打开配置文件: " + filepath);
|
||||
}
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
std::string content = buffer.str();
|
||||
file.close();
|
||||
|
||||
return loadFromString(content, config);
|
||||
}
|
||||
|
||||
ConfigSaveResult JsonConfigLoader::save(const std::string& filepath, const AppConfig& config) {
|
||||
E2D_LOG_INFO("正在保存应用配置到 JSON 文件: {}", filepath);
|
||||
|
||||
std::string content = saveToString(config);
|
||||
|
||||
std::ofstream file(filepath);
|
||||
if (!file.is_open()) {
|
||||
E2D_LOG_ERROR("无法创建配置文件: {}", filepath);
|
||||
return ConfigSaveResult::error("无法创建配置文件: " + filepath);
|
||||
}
|
||||
|
||||
file << content;
|
||||
file.close();
|
||||
|
||||
E2D_LOG_INFO("配置已成功保存到: {}", filepath);
|
||||
return ConfigSaveResult::ok();
|
||||
}
|
||||
|
||||
ConfigLoadResult JsonConfigLoader::loadFromString(const std::string& content, AppConfig& config) {
|
||||
json root;
|
||||
|
||||
try {
|
||||
root = json::parse(content);
|
||||
} catch (const json::parse_error& e) {
|
||||
E2D_LOG_ERROR("JSON 解析错误: {}", e.what());
|
||||
return ConfigLoadResult::error(std::string("JSON 解析错误: ") + e.what(),
|
||||
static_cast<int>(e.byte));
|
||||
}
|
||||
|
||||
if (root.contains("appName") && root["appName"].is_string()) {
|
||||
config.appName = root["appName"].get<std::string>();
|
||||
}
|
||||
|
||||
if (root.contains("appVersion") && root["appVersion"].is_string()) {
|
||||
config.appVersion = root["appVersion"].get<std::string>();
|
||||
}
|
||||
|
||||
if (root.contains("organization") && root["organization"].is_string()) {
|
||||
config.organization = root["organization"].get<std::string>();
|
||||
}
|
||||
|
||||
if (root.contains("configFile") && root["configFile"].is_string()) {
|
||||
config.configFile = root["configFile"].get<std::string>();
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("JSON 应用配置加载成功");
|
||||
return ConfigLoadResult::ok();
|
||||
}
|
||||
|
||||
std::string JsonConfigLoader::saveToString(const AppConfig& config) {
|
||||
json root;
|
||||
|
||||
root["appName"] = config.appName;
|
||||
root["appVersion"] = config.appVersion;
|
||||
root["organization"] = config.organization;
|
||||
root["configFile"] = config.configFile;
|
||||
|
||||
return root.dump(4);
|
||||
}
|
||||
|
||||
ConfigLoadResult JsonConfigLoader::loadWithModules(const std::string& filepath) {
|
||||
E2D_LOG_INFO("正在从 JSON 文件加载完整配置: {}", filepath);
|
||||
|
||||
std::ifstream file(filepath);
|
||||
if (!file.is_open()) {
|
||||
E2D_LOG_ERROR("无法打开配置文件: {}", filepath);
|
||||
return ConfigLoadResult::error("无法打开配置文件: " + filepath);
|
||||
}
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
std::string content = buffer.str();
|
||||
file.close();
|
||||
|
||||
json root;
|
||||
try {
|
||||
root = json::parse(content);
|
||||
} catch (const json::parse_error& e) {
|
||||
E2D_LOG_ERROR("JSON 解析错误: {}", e.what());
|
||||
return ConfigLoadResult::error(std::string("JSON 解析错误: ") + e.what(),
|
||||
static_cast<int>(e.byte));
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("完整配置加载成功");
|
||||
return ConfigLoadResult::ok();
|
||||
}
|
||||
|
||||
ConfigSaveResult JsonConfigLoader::saveWithModules(const std::string& filepath) {
|
||||
E2D_LOG_INFO("正在保存完整配置到 JSON 文件: {}", filepath);
|
||||
|
||||
json root;
|
||||
|
||||
std::ofstream file(filepath);
|
||||
if (!file.is_open()) {
|
||||
E2D_LOG_ERROR("无法创建配置文件: {}", filepath);
|
||||
return ConfigSaveResult::error("无法创建配置文件: " + filepath);
|
||||
}
|
||||
|
||||
file << root.dump(4);
|
||||
file.close();
|
||||
|
||||
E2D_LOG_INFO("完整配置已成功保存到: {}", filepath);
|
||||
return ConfigSaveResult::ok();
|
||||
}
|
||||
|
||||
bool JsonConfigLoader::supportsFile(const std::string& filepath) const {
|
||||
if (filepath.length() >= 5) {
|
||||
std::string ext = filepath.substr(filepath.length() - 5);
|
||||
for (char& c : ext) c = static_cast<char>(std::tolower(c));
|
||||
return ext == ".json";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
UniquePtr<ConfigLoader> JsonConfigLoader::clone() const {
|
||||
return makeUnique<JsonConfigLoader>();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,366 +0,0 @@
|
|||
#include <extra2d/config/config_manager.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
ConfigManager::ConfigManager()
|
||||
: m_nextCallbackId(1)
|
||||
, m_autoSaveEnabled(false)
|
||||
, m_autoSaveInterval(30.0f)
|
||||
, m_autoSaveTimer(0.0f)
|
||||
{
|
||||
m_appConfig = AppConfig::createDefault();
|
||||
}
|
||||
|
||||
ConfigManager::~ConfigManager() {
|
||||
if (m_initialized) {
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
ConfigManager& ConfigManager::instance() {
|
||||
static ConfigManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool ConfigManager::initialize(const std::string& configPath) {
|
||||
if (m_initialized) {
|
||||
E2D_LOG_WARN("ConfigManager already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
m_configPath = configPath;
|
||||
|
||||
m_loader = makeUnique<JsonConfigLoader>();
|
||||
if (!m_loader) {
|
||||
E2D_LOG_ERROR("Failed to create config loader");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_appConfig = AppConfig::createDefault();
|
||||
|
||||
m_initialized = true;
|
||||
m_modified = false;
|
||||
|
||||
E2D_LOG_INFO("ConfigManager initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConfigManager::shutdown() {
|
||||
if (!m_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
if (m_autoSaveEnabled && m_modified) {
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
m_changeCallbacks.clear();
|
||||
m_rawValues.clear();
|
||||
m_loader.reset();
|
||||
|
||||
m_initialized = false;
|
||||
m_modified = false;
|
||||
|
||||
E2D_LOG_INFO("ConfigManager shutdown complete");
|
||||
}
|
||||
|
||||
bool ConfigManager::isInitialized() const {
|
||||
return m_initialized;
|
||||
}
|
||||
|
||||
ConfigLoadResult ConfigManager::loadConfig(const std::string& filepath) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
std::string path = filepath.empty() ? m_configPath : filepath;
|
||||
if (path.empty()) {
|
||||
return ConfigLoadResult::error("No config file path specified");
|
||||
}
|
||||
|
||||
if (!m_loader) {
|
||||
return ConfigLoadResult::error("Config loader not initialized");
|
||||
}
|
||||
|
||||
AppConfig loadedConfig;
|
||||
ConfigLoadResult result = m_loader->load(path, loadedConfig);
|
||||
|
||||
if (result.success) {
|
||||
m_appConfig.merge(loadedConfig);
|
||||
|
||||
if (!m_appConfig.validate()) {
|
||||
E2D_LOG_WARN("Loaded config validation failed, using defaults");
|
||||
m_appConfig = AppConfig::createDefault();
|
||||
}
|
||||
|
||||
m_configPath = path;
|
||||
m_modified = false;
|
||||
|
||||
E2D_LOG_INFO("Config loaded from: {}", path);
|
||||
} else {
|
||||
E2D_LOG_ERROR("Failed to load config from {}: {}", path, result.errorMessage);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ConfigSaveResult ConfigManager::saveConfig(const std::string& filepath) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
std::string path = filepath.empty() ? m_configPath : filepath;
|
||||
if (path.empty()) {
|
||||
return ConfigSaveResult::error("No config file path specified");
|
||||
}
|
||||
|
||||
if (!m_loader) {
|
||||
return ConfigSaveResult::error("Config loader not initialized");
|
||||
}
|
||||
|
||||
ConfigSaveResult result = m_loader->save(path, m_appConfig);
|
||||
|
||||
if (result.success) {
|
||||
m_configPath = path;
|
||||
m_modified = false;
|
||||
|
||||
E2D_LOG_INFO("Config saved to: {}", path);
|
||||
} else {
|
||||
E2D_LOG_ERROR("Failed to save config to {}: {}", path, result.errorMessage);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ConfigLoadResult ConfigManager::loadConfigWithModules(const std::string& filepath) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
std::string path = filepath.empty() ? m_configPath : filepath;
|
||||
if (path.empty()) {
|
||||
return ConfigLoadResult::error("No config file path specified");
|
||||
}
|
||||
|
||||
if (!m_loader) {
|
||||
return ConfigLoadResult::error("Config loader not initialized");
|
||||
}
|
||||
|
||||
ConfigLoadResult result = m_loader->loadWithModules(path);
|
||||
|
||||
if (result.success) {
|
||||
m_configPath = path;
|
||||
m_modified = false;
|
||||
E2D_LOG_INFO("Full config (with modules) loaded from: {}", path);
|
||||
} else {
|
||||
E2D_LOG_ERROR("Failed to load full config from {}: {}", path, result.errorMessage);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ConfigSaveResult ConfigManager::saveConfigWithModules(const std::string& filepath) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
std::string path = filepath.empty() ? m_configPath : filepath;
|
||||
if (path.empty()) {
|
||||
return ConfigSaveResult::error("No config file path specified");
|
||||
}
|
||||
|
||||
if (!m_loader) {
|
||||
return ConfigSaveResult::error("Config loader not initialized");
|
||||
}
|
||||
|
||||
ConfigSaveResult result = m_loader->saveWithModules(path);
|
||||
|
||||
if (result.success) {
|
||||
m_configPath = path;
|
||||
m_modified = false;
|
||||
E2D_LOG_INFO("Full config (with modules) saved to: {}", path);
|
||||
} else {
|
||||
E2D_LOG_ERROR("Failed to save full config to {}: {}", path, result.errorMessage);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ConfigLoadResult ConfigManager::reload() {
|
||||
return loadConfig(m_configPath);
|
||||
}
|
||||
|
||||
const AppConfig& ConfigManager::appConfig() const {
|
||||
return m_appConfig;
|
||||
}
|
||||
|
||||
AppConfig& ConfigManager::appConfig() {
|
||||
m_modified = true;
|
||||
return m_appConfig;
|
||||
}
|
||||
|
||||
void ConfigManager::setAppConfig(const AppConfig& config) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
m_appConfig = config;
|
||||
m_modified = true;
|
||||
|
||||
E2D_LOG_INFO("App config updated");
|
||||
}
|
||||
|
||||
int ConfigManager::registerChangeCallback(ConfigChangeCallback callback) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
int id = m_nextCallbackId++;
|
||||
m_changeCallbacks[id] = std::move(callback);
|
||||
|
||||
E2D_LOG_DEBUG("Registered config change callback with id {}", id);
|
||||
return id;
|
||||
}
|
||||
|
||||
void ConfigManager::unregisterChangeCallback(int callbackId) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
auto it = m_changeCallbacks.find(callbackId);
|
||||
if (it != m_changeCallbacks.end()) {
|
||||
m_changeCallbacks.erase(it);
|
||||
E2D_LOG_DEBUG("Unregistered config change callback {}", callbackId);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigManager::clearChangeCallbacks() {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_changeCallbacks.clear();
|
||||
E2D_LOG_DEBUG("Cleared all config change callbacks");
|
||||
}
|
||||
|
||||
void ConfigManager::setValue(const std::string& section, const std::string& key, const std::string& value) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
std::string fullKey = section + "." + key;
|
||||
|
||||
ConfigChangeEvent event;
|
||||
event.section = section;
|
||||
event.field = key;
|
||||
event.oldValue = m_rawValues[fullKey];
|
||||
event.newValue = value;
|
||||
|
||||
m_rawValues[fullKey] = value;
|
||||
m_modified = true;
|
||||
notifyChangeCallbacks(event);
|
||||
}
|
||||
|
||||
void ConfigManager::setValue(const std::string& section, const std::string& key, int value) {
|
||||
std::ostringstream oss;
|
||||
oss << value;
|
||||
setValue(section, key, oss.str());
|
||||
}
|
||||
|
||||
void ConfigManager::setValue(const std::string& section, const std::string& key, float value) {
|
||||
std::ostringstream oss;
|
||||
oss << value;
|
||||
setValue(section, key, oss.str());
|
||||
}
|
||||
|
||||
void ConfigManager::setValue(const std::string& section, const std::string& key, bool value) {
|
||||
setValue(section, key, std::string(value ? "true" : "false"));
|
||||
}
|
||||
|
||||
std::string ConfigManager::getValue(const std::string& section, const std::string& key,
|
||||
const std::string& defaultValue) const {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
std::string fullKey = section + "." + key;
|
||||
auto it = m_rawValues.find(fullKey);
|
||||
if (it != m_rawValues.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
int ConfigManager::getIntValue(const std::string& section, const std::string& key, int defaultValue) const {
|
||||
std::string value = getValue(section, key);
|
||||
if (!value.empty()) {
|
||||
try {
|
||||
return std::stoi(value);
|
||||
} catch (...) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
float ConfigManager::getFloatValue(const std::string& section, const std::string& key, float defaultValue) const {
|
||||
std::string value = getValue(section, key);
|
||||
if (!value.empty()) {
|
||||
try {
|
||||
return std::stof(value);
|
||||
} catch (...) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
bool ConfigManager::getBoolValue(const std::string& section, const std::string& key, bool defaultValue) const {
|
||||
std::string value = getValue(section, key);
|
||||
if (!value.empty()) {
|
||||
if (value == "true" || value == "1") return true;
|
||||
if (value == "false" || value == "0") return false;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
void ConfigManager::resetToDefaults() {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
m_appConfig = AppConfig::createDefault();
|
||||
m_rawValues.clear();
|
||||
m_modified = true;
|
||||
|
||||
E2D_LOG_INFO("Config reset to defaults");
|
||||
}
|
||||
|
||||
bool ConfigManager::hasUnsavedChanges() const {
|
||||
return m_modified;
|
||||
}
|
||||
|
||||
void ConfigManager::markModified() {
|
||||
m_modified = true;
|
||||
}
|
||||
|
||||
void ConfigManager::clearModified() {
|
||||
m_modified = false;
|
||||
}
|
||||
|
||||
void ConfigManager::setAutoSave(bool enabled, float interval) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
m_autoSaveEnabled = enabled;
|
||||
m_autoSaveInterval = interval > 0.0f ? interval : 30.0f;
|
||||
m_autoSaveTimer = 0.0f;
|
||||
|
||||
E2D_LOG_INFO("Auto save {} (interval: {}s)",
|
||||
enabled ? "enabled" : "disabled", m_autoSaveInterval);
|
||||
}
|
||||
|
||||
void ConfigManager::update(float deltaTime) {
|
||||
if (!m_autoSaveEnabled || !m_modified) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_autoSaveTimer += deltaTime;
|
||||
|
||||
if (m_autoSaveTimer >= m_autoSaveInterval) {
|
||||
m_autoSaveTimer = 0.0f;
|
||||
saveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigManager::notifyChangeCallbacks(const ConfigChangeEvent& event) {
|
||||
for (const auto& pair : m_changeCallbacks) {
|
||||
if (pair.second) {
|
||||
pair.second(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
#include <extra2d/core/registry.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <algorithm>
|
||||
#include <queue>
|
||||
#include <unordered_set>
|
||||
#include <iostream>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
Registry& Registry::instance() {
|
||||
static Registry instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool Registry::init() {
|
||||
auto sorted = topologicalSort();
|
||||
|
||||
std::cout << "[Registry] Initializing " << sorted.size() << " modules..." << std::endl;
|
||||
|
||||
for (auto* module : sorted) {
|
||||
std::cout << "[Registry] Initializing module: " << module->name() << std::endl;
|
||||
if (!module->init()) {
|
||||
std::cerr << "[Registry] Failed to initialize module: " << module->name() << std::endl;
|
||||
return false;
|
||||
}
|
||||
std::cout << "[Registry] Module " << module->name() << " initialized successfully" << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "[Registry] All modules initialized" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Registry::shutdown() {
|
||||
auto sorted = topologicalSort();
|
||||
|
||||
// 反向关闭
|
||||
for (auto it = sorted.rbegin(); it != sorted.rend(); ++it) {
|
||||
(*it)->shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void Registry::clear() {
|
||||
shutdown();
|
||||
modules_.clear();
|
||||
}
|
||||
|
||||
std::vector<Module*> Registry::topologicalSort() {
|
||||
std::vector<Module*> result;
|
||||
std::unordered_map<Module*, int> inDegree;
|
||||
std::unordered_map<Module*, std::vector<Module*>> adj;
|
||||
|
||||
// 构建图
|
||||
for (auto& [typeIdx, module] : modules_) {
|
||||
inDegree[module.get()] = 0;
|
||||
}
|
||||
|
||||
for (auto& [typeIdx, module] : modules_) {
|
||||
for (auto& depType : module->deps()) {
|
||||
Module* dep = get(depType);
|
||||
if (dep) {
|
||||
adj[dep].push_back(module.get());
|
||||
inDegree[module.get()]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 优先级队列(优先级小的先处理)
|
||||
auto cmp = [](Module* a, Module* b) {
|
||||
return a->priority() > b->priority();
|
||||
};
|
||||
std::priority_queue<Module*, std::vector<Module*>, decltype(cmp)> pq(cmp);
|
||||
|
||||
for (auto& [mod, degree] : inDegree) {
|
||||
if (degree == 0) {
|
||||
pq.push(mod);
|
||||
}
|
||||
}
|
||||
|
||||
// 拓扑排序
|
||||
while (!pq.empty()) {
|
||||
Module* curr = pq.top();
|
||||
pq.pop();
|
||||
result.push_back(curr);
|
||||
|
||||
for (Module* next : adj[curr]) {
|
||||
inDegree[next]--;
|
||||
if (inDegree[next] == 0) {
|
||||
pq.push(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#include <algorithm>
|
||||
#include <extra2d/graphics/camera.h>
|
||||
#include <extra2d/graphics/viewport_adapter.h>
|
||||
#include <extra2d/graphics/camera/camera.h>
|
||||
#include <extra2d/graphics/camera/viewport_adapter.h>
|
||||
#include <glm/gtc/matrix_inverse.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#include <extra2d/graphics/viewport_adapter.h>
|
||||
#include <extra2d/graphics/camera/viewport_adapter.h>
|
||||
#include <algorithm>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/graphics/core/render_backend.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#include <extra2d/graphics/render_command.h>
|
||||
#include <extra2d/graphics/core/render_command.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace extra2d {
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
#include <extra2d/graphics/core/render_module.h>
|
||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
||||
#include <extra2d/graphics/opengl/gl_shader.h>
|
||||
#include <extra2d/graphics/shader/shader_manager.h>
|
||||
#include <extra2d/core/registry.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/app/application.h>
|
||||
#include <extra2d/platform/window_module.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <SDL.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
RenderModule::RenderModule(const Cfg& cfg) : cfg_(cfg) {}
|
||||
|
||||
RenderModule::~RenderModule() {
|
||||
if (initialized_) {
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
static std::string getExecutableDir() {
|
||||
char* basePath = SDL_GetBasePath();
|
||||
if (basePath) {
|
||||
std::string path(basePath);
|
||||
SDL_free(basePath);
|
||||
return path;
|
||||
}
|
||||
return "./";
|
||||
}
|
||||
|
||||
bool RenderModule::init() {
|
||||
if (initialized_) return true;
|
||||
|
||||
// 获取WindowModule依赖
|
||||
auto* winMod = Registry::instance().get<WindowModule>();
|
||||
if (!winMod || !winMod->win()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 初始化ShaderManager
|
||||
if (!ShaderManager::getInstance().isInitialized()) {
|
||||
auto factory = makeShared<GLShaderFactory>();
|
||||
std::string shaderDir = getExecutableDir() + "shaders/";
|
||||
std::string cacheDir = getExecutableDir() + "shader_cache/";
|
||||
if (!ShaderManager::getInstance().init(shaderDir, cacheDir, factory)) {
|
||||
E2D_LOG_WARN("Failed to initialize ShaderManager with dir: {}", shaderDir);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建渲染后端
|
||||
renderer_ = RenderBackend::create(cfg_.backend);
|
||||
if (!renderer_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 初始化渲染器
|
||||
if (!renderer_->init(winMod->win())) {
|
||||
renderer_.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderModule::shutdown() {
|
||||
if (!initialized_) return;
|
||||
|
||||
if (renderer_) {
|
||||
renderer_->shutdown();
|
||||
renderer_.reset();
|
||||
}
|
||||
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#include <glad/glad.h>
|
||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
||||
#include <extra2d/graphics/render_target.h>
|
||||
#include <extra2d/graphics/core/render_target.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#include <extra2d/graphics/gpu_context.h>
|
||||
#include <extra2d/graphics/memory/gpu_context.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#include <algorithm>
|
||||
#include <extra2d/graphics/vram_manager.h>
|
||||
#include <extra2d/graphics/memory/vram_manager.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
|
@ -2,12 +2,12 @@
|
|||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <extra2d/graphics/gpu_context.h>
|
||||
#include <extra2d/graphics/memory/gpu_context.h>
|
||||
#include <extra2d/graphics/opengl/gl_font_atlas.h>
|
||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
||||
#include <extra2d/graphics/shader_manager.h>
|
||||
#include <extra2d/graphics/vram_manager.h>
|
||||
#include <extra2d/graphics/shader/shader_manager.h>
|
||||
#include <extra2d/graphics/memory/vram_manager.h>
|
||||
#include <extra2d/platform/iwindow.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <vector>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#include <extra2d/graphics/opengl/gl_texture.h>
|
||||
#include <extra2d/graphics/gpu_context.h>
|
||||
#include <extra2d/graphics/vram_manager.h>
|
||||
#include <extra2d/graphics/memory/gpu_context.h>
|
||||
#include <extra2d/graphics/memory/vram_manager.h>
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <cstring>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,286 @@
|
|||
#include <extra2d/graphics/shader/shader_cache.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return 缓存管理器实例引用
|
||||
*/
|
||||
ShaderCache& ShaderCache::getInstance() {
|
||||
static ShaderCache instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化缓存系统
|
||||
* @param cacheDir 缓存目录路径
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderCache::init(const std::string& cacheDir) {
|
||||
cacheDir_ = cacheDir;
|
||||
|
||||
if (!ensureCacheDirectory()) {
|
||||
E2D_LOG_ERROR("Failed to create cache directory: {}", cacheDir);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadCacheIndex()) {
|
||||
E2D_LOG_WARN("Failed to load cache index, starting fresh");
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
E2D_LOG_INFO("Shader cache initialized at: {}", cacheDir);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 关闭缓存系统
|
||||
*/
|
||||
void ShaderCache::shutdown() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
saveCacheIndex();
|
||||
cacheMap_.clear();
|
||||
initialized_ = false;
|
||||
E2D_LOG_INFO("Shader cache shutdown");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查缓存是否有效
|
||||
* @param name Shader名称
|
||||
* @param sourceHash 源码哈希值
|
||||
* @return 缓存有效返回true,否则返回false
|
||||
*/
|
||||
bool ShaderCache::hasValidCache(const std::string& name, const std::string& sourceHash) {
|
||||
auto it = cacheMap_.find(name);
|
||||
if (it == cacheMap_.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return it->second.sourceHash == sourceHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 加载缓存的二进制数据
|
||||
* @param name Shader名称
|
||||
* @return 缓存条目指针,不存在返回nullptr
|
||||
*/
|
||||
Ptr<ShaderCacheEntry> ShaderCache::loadCache(const std::string& name) {
|
||||
auto it = cacheMap_.find(name);
|
||||
if (it == cacheMap_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string cachePath = getCachePath(name);
|
||||
std::ifstream file(cachePath, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
E2D_LOG_WARN("Failed to open cache file: {}", cachePath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto entry = std::make_shared<ShaderCacheEntry>(it->second);
|
||||
entry->binary.clear();
|
||||
|
||||
file.seekg(0, std::ios::end);
|
||||
size_t fileSize = static_cast<size_t>(file.tellg());
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
entry->binary.resize(fileSize);
|
||||
file.read(reinterpret_cast<char*>(entry->binary.data()), fileSize);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 保存编译结果到缓存
|
||||
* @param entry 缓存条目
|
||||
* @return 保存成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderCache::saveCache(const ShaderCacheEntry& entry) {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_WARN("ShaderCache not initialized, cannot save cache");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.binary.empty()) {
|
||||
E2D_LOG_WARN("Shader binary is empty, skipping cache save for: {}", entry.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string cachePath = getCachePath(entry.name);
|
||||
E2D_LOG_DEBUG("Saving shader cache to: {} ({} bytes)", cachePath, entry.binary.size());
|
||||
|
||||
std::ofstream file(cachePath, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
E2D_LOG_ERROR("Failed to create cache file: {}", cachePath);
|
||||
return false;
|
||||
}
|
||||
|
||||
file.write(reinterpret_cast<const char*>(entry.binary.data()), entry.binary.size());
|
||||
file.close();
|
||||
|
||||
cacheMap_[entry.name] = entry;
|
||||
saveCacheIndex();
|
||||
|
||||
E2D_LOG_INFO("Shader cache saved: {} ({} bytes)", entry.name, entry.binary.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 使缓存失效
|
||||
* @param name Shader名称
|
||||
*/
|
||||
void ShaderCache::invalidate(const std::string& name) {
|
||||
auto it = cacheMap_.find(name);
|
||||
if (it == cacheMap_.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string cachePath = getCachePath(name);
|
||||
fs::remove(cachePath);
|
||||
|
||||
cacheMap_.erase(it);
|
||||
saveCacheIndex();
|
||||
|
||||
E2D_LOG_DEBUG("Shader cache invalidated: {}", name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清除所有缓存
|
||||
*/
|
||||
void ShaderCache::clearAll() {
|
||||
for (const auto& pair : cacheMap_) {
|
||||
std::string cachePath = getCachePath(pair.first);
|
||||
fs::remove(cachePath);
|
||||
}
|
||||
|
||||
cacheMap_.clear();
|
||||
saveCacheIndex();
|
||||
|
||||
E2D_LOG_INFO("All shader caches cleared");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算源码哈希值
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return 哈希值字符串
|
||||
*/
|
||||
std::string ShaderCache::computeHash(const std::string& vertSource,
|
||||
const std::string& fragSource) {
|
||||
std::string combined = vertSource + fragSource;
|
||||
|
||||
uint32_t hash = 5381;
|
||||
for (char c : combined) {
|
||||
hash = ((hash << 5) + hash) + static_cast<uint32_t>(c);
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::hex << hash;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 加载缓存索引
|
||||
* @return 加载成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderCache::loadCacheIndex() {
|
||||
std::string indexPath = cacheDir_ + "/.cache_index";
|
||||
|
||||
if (!fs::exists(indexPath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ifstream file(indexPath);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
if (line.empty() || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t pos = line.find('=');
|
||||
if (pos == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string name = line.substr(0, pos);
|
||||
std::string hash = line.substr(pos + 1);
|
||||
|
||||
std::string cachePath = getCachePath(name);
|
||||
if (fs::exists(cachePath)) {
|
||||
ShaderCacheEntry entry;
|
||||
entry.name = name;
|
||||
entry.sourceHash = hash;
|
||||
entry.compileTime = static_cast<uint64_t>(
|
||||
std::chrono::system_clock::now().time_since_epoch().count());
|
||||
cacheMap_[name] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 保存缓存索引
|
||||
* @return 保存成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderCache::saveCacheIndex() {
|
||||
std::string indexPath = cacheDir_ + "/.cache_index";
|
||||
|
||||
std::ofstream file(indexPath);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file << "# Extra2D Shader Cache Index\n";
|
||||
file << "# Format: name=hash\n";
|
||||
|
||||
for (const auto& pair : cacheMap_) {
|
||||
file << pair.first << "=" << pair.second.sourceHash << "\n";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取缓存文件路径
|
||||
* @param name Shader名称
|
||||
* @return 缓存文件完整路径
|
||||
*/
|
||||
std::string ShaderCache::getCachePath(const std::string& name) const {
|
||||
return cacheDir_ + "/" + name + ".cache";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 确保缓存目录存在
|
||||
* @return 目录存在或创建成功返回true,否则返回false
|
||||
*/
|
||||
bool ShaderCache::ensureCacheDirectory() {
|
||||
if (cacheDir_.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
if (!fs::exists(cacheDir_)) {
|
||||
if (!fs::create_directories(cacheDir_, ec)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
#include <extra2d/graphics/shader/shader_hot_reloader.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return 热重载管理器实例引用
|
||||
*/
|
||||
ShaderHotReloader& ShaderHotReloader::getInstance() {
|
||||
static ShaderHotReloader instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化热重载系统
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderHotReloader::init() {
|
||||
if (initialized_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
buffer_.resize(4096);
|
||||
#endif
|
||||
|
||||
initialized_ = true;
|
||||
E2D_LOG_INFO("Shader hot reloader initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 关闭热重载系统
|
||||
*/
|
||||
void ShaderHotReloader::shutdown() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (watchHandle_ != nullptr) {
|
||||
FindCloseChangeNotification(watchHandle_);
|
||||
watchHandle_ = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
watchMap_.clear();
|
||||
initialized_ = false;
|
||||
enabled_ = false;
|
||||
E2D_LOG_INFO("Shader hot reloader shutdown");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册Shader文件监视
|
||||
* @param shaderName Shader名称
|
||||
* @param filePaths 要监视的文件列表
|
||||
* @param callback 文件变化时的回调
|
||||
*/
|
||||
void ShaderHotReloader::watch(const std::string& shaderName,
|
||||
const std::vector<std::string>& filePaths,
|
||||
FileChangeCallback callback) {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_WARN("Hot reloader not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
WatchInfo info;
|
||||
info.filePaths = filePaths;
|
||||
info.callback = callback;
|
||||
|
||||
for (const auto& path : filePaths) {
|
||||
info.modifiedTimes[path] = getFileModifiedTime(path);
|
||||
}
|
||||
|
||||
watchMap_[shaderName] = std::move(info);
|
||||
E2D_LOG_DEBUG("Watching shader: {} ({} files)", shaderName, filePaths.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 取消监视
|
||||
* @param shaderName Shader名称
|
||||
*/
|
||||
void ShaderHotReloader::unwatch(const std::string& shaderName) {
|
||||
auto it = watchMap_.find(shaderName);
|
||||
if (it != watchMap_.end()) {
|
||||
watchMap_.erase(it);
|
||||
E2D_LOG_DEBUG("Stopped watching shader: {}", shaderName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新文件监视(在主循环中调用)
|
||||
*/
|
||||
void ShaderHotReloader::update() {
|
||||
if (!initialized_ || !enabled_) {
|
||||
return;
|
||||
}
|
||||
|
||||
pollChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 启用/禁用热重载
|
||||
* @param enabled 是否启用
|
||||
*/
|
||||
void ShaderHotReloader::setEnabled(bool enabled) {
|
||||
enabled_ = enabled;
|
||||
E2D_LOG_DEBUG("Hot reload {}", enabled ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 轮询检查文件变化
|
||||
*/
|
||||
void ShaderHotReloader::pollChanges() {
|
||||
auto now = static_cast<uint64_t>(
|
||||
std::chrono::system_clock::now().time_since_epoch().count());
|
||||
|
||||
for (auto& pair : watchMap_) {
|
||||
WatchInfo& info = pair.second;
|
||||
|
||||
for (const auto& filePath : info.filePaths) {
|
||||
uint64_t currentModTime = getFileModifiedTime(filePath);
|
||||
uint64_t lastModTime = info.modifiedTimes[filePath];
|
||||
|
||||
if (currentModTime != 0 && lastModTime != 0 && currentModTime != lastModTime) {
|
||||
info.modifiedTimes[filePath] = currentModTime;
|
||||
|
||||
FileChangeEvent event;
|
||||
event.filepath = filePath;
|
||||
event.type = FileChangeEvent::Type::Modified;
|
||||
event.timestamp = now;
|
||||
|
||||
E2D_LOG_DEBUG("Shader file changed: {}", filePath);
|
||||
|
||||
if (info.callback) {
|
||||
info.callback(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取文件修改时间
|
||||
* @param filepath 文件路径
|
||||
* @return 修改时间戳
|
||||
*/
|
||||
uint64_t ShaderHotReloader::getFileModifiedTime(const std::string& filepath) {
|
||||
try {
|
||||
auto ftime = fs::last_write_time(filepath);
|
||||
auto sctp = std::chrono::time_point_cast<std::chrono::seconds>(
|
||||
ftime - fs::file_time_type::clock::now() + std::chrono::system_clock::now());
|
||||
return static_cast<uint64_t>(sctp.time_since_epoch().count());
|
||||
} catch (...) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#include <extra2d/graphics/shader_loader.h>
|
||||
#include <extra2d/graphics/shader/shader_loader.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#include <extra2d/graphics/shader_manager.h>
|
||||
#include <extra2d/graphics/shader/shader_manager.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
#include <extra2d/graphics/shader/shader_manager.h>
|
||||
#include <extra2d/graphics/shader/shader_preset.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 创建水波纹效果着色器
|
||||
* @param params 水波纹效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
Ptr<IShader> ShaderPreset::Water(const WaterParams ¶ms) {
|
||||
Ptr<IShader> shader = ShaderManager::getInstance().get("water");
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to get water shader");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
shader->setFloat("u_waveSpeed", params.waveSpeed);
|
||||
shader->setFloat("u_waveAmplitude", params.waveAmplitude);
|
||||
shader->setFloat("u_waveFrequency", params.waveFrequency);
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建描边效果着色器
|
||||
* @param params 描边效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
Ptr<IShader> ShaderPreset::Outline(const OutlineParams ¶ms) {
|
||||
Ptr<IShader> shader = ShaderManager::getInstance().get("outline");
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to get outline shader");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
shader->setVec4("u_outlineColor", glm::vec4(params.color.r, params.color.g,
|
||||
params.color.b, params.color.a));
|
||||
shader->setFloat("u_thickness", params.thickness);
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建扭曲效果着色器
|
||||
* @param params 扭曲效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
Ptr<IShader> ShaderPreset::Distortion(const DistortionParams ¶ms) {
|
||||
Ptr<IShader> shader = ShaderManager::getInstance().get("distortion");
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to get distortion shader");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
shader->setFloat("u_distortionAmount", params.distortionAmount);
|
||||
shader->setFloat("u_timeScale", params.timeScale);
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建像素化效果着色器
|
||||
* @param params 像素化效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
Ptr<IShader> ShaderPreset::Pixelate(const PixelateParams ¶ms) {
|
||||
Ptr<IShader> shader = ShaderManager::getInstance().get("pixelate");
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to get pixelate shader");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
shader->setFloat("u_pixelSize", params.pixelSize);
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建反相效果着色器
|
||||
* @param params 反相效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
Ptr<IShader> ShaderPreset::Invert(const InvertParams ¶ms) {
|
||||
Ptr<IShader> shader = ShaderManager::getInstance().get("invert");
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to get invert shader");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
shader->setFloat("u_strength", params.strength);
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建灰度效果着色器
|
||||
* @param params 灰度效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
Ptr<IShader> ShaderPreset::Grayscale(const GrayscaleParams ¶ms) {
|
||||
Ptr<IShader> shader = ShaderManager::getInstance().get("grayscale");
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to get grayscale shader");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
shader->setFloat("u_intensity", params.intensity);
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建模糊效果着色器
|
||||
* @param params 模糊效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
Ptr<IShader> ShaderPreset::Blur(const BlurParams ¶ms) {
|
||||
Ptr<IShader> shader = ShaderManager::getInstance().get("blur");
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to get blur shader");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
shader->setFloat("u_radius", params.radius);
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建灰度+描边组合效果着色器
|
||||
* @param grayParams 灰度效果参数
|
||||
* @param outlineParams 描边效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
Ptr<IShader>
|
||||
ShaderPreset::GrayscaleOutline(const GrayscaleParams &grayParams,
|
||||
const OutlineParams &outlineParams) {
|
||||
std::string shaderDir = ShaderManager::getInstance().getShaderDir();
|
||||
std::string shaderPath = shaderDir + "effects/grayscale_outline.shader";
|
||||
|
||||
Ptr<IShader> shader =
|
||||
ShaderManager::getInstance().loadFromCombinedFile(shaderPath);
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to load grayscale_outline shader from: {}",
|
||||
shaderPath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
shader->setFloat("u_grayIntensity", grayParams.intensity);
|
||||
shader->setVec4("u_outlineColor",
|
||||
glm::vec4(outlineParams.color.r, outlineParams.color.g,
|
||||
outlineParams.color.b, outlineParams.color.a));
|
||||
shader->setFloat("u_thickness", outlineParams.thickness);
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建像素化+反相组合效果着色器
|
||||
* @param pixParams 像素化效果参数
|
||||
* @param invParams 反相效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
Ptr<IShader> ShaderPreset::PixelateInvert(const PixelateParams &pixParams,
|
||||
const InvertParams &invParams) {
|
||||
std::string shaderDir = ShaderManager::getInstance().getShaderDir();
|
||||
std::string shaderPath = shaderDir + "effects/pixelate_invert.shader";
|
||||
|
||||
Ptr<IShader> shader =
|
||||
ShaderManager::getInstance().loadFromCombinedFile(shaderPath);
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to load pixelate_invert shader from: {}", shaderPath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
shader->setFloat("u_pixelSize", pixParams.pixelSize);
|
||||
shader->setFloat("u_invertStrength", invParams.strength);
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#include <extra2d/graphics/alpha_mask.h>
|
||||
#include <extra2d/graphics/texture/alpha_mask.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#include <extra2d/graphics/texture_atlas.h>
|
||||
#include <extra2d/graphics/texture/texture_atlas.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#include <extra2d/graphics/texture_pool.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/graphics/texture/texture_pool.h>
|
||||
#include <extra2d/graphics/core/render_backend.h>
|
||||
#include <extra2d/scene/scene.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <extra2d/graphics/render_command.h>
|
||||
#include <extra2d/graphics/core/render_command.h>
|
||||
#include <extra2d/scene/node.h>
|
||||
#include <extra2d/scene/scene.h>
|
||||
#include <extra2d/services/event_service.h>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/graphics/render_command.h>
|
||||
#include <extra2d/graphics/core/render_backend.h>
|
||||
#include <extra2d/graphics/core/render_command.h>
|
||||
#include <extra2d/scene/scene.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#include <algorithm>
|
||||
#include <extra2d/app/application.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/graphics/render_command.h>
|
||||
#include <extra2d/graphics/core/render_backend.h>
|
||||
#include <extra2d/graphics/core/render_command.h>
|
||||
#include <extra2d/platform/iinput.h>
|
||||
#include <extra2d/scene/scene_manager.h>
|
||||
#include <extra2d/scene/transition_box_scene.h>
|
||||
|
|
@ -545,7 +545,8 @@ bool SceneManager::hasScene(const std::string &name) const {
|
|||
* 更新当前场景并分发指针事件
|
||||
*/
|
||||
void SceneManager::update(float dt) {
|
||||
if (isTransitioning_) {
|
||||
if (isTransitioning_ && activeTransitionScene_) {
|
||||
activeTransitionScene_->updateTransition(dt);
|
||||
hoverTarget_ = nullptr;
|
||||
captureTarget_ = nullptr;
|
||||
hasLastPointerWorld_ = false;
|
||||
|
|
@ -746,8 +747,9 @@ void SceneManager::finishTransition() {
|
|||
* 处理鼠标悬停、移动、点击和滚轮事件
|
||||
*/
|
||||
void SceneManager::dispatchPointerEvents(Scene &scene) {
|
||||
auto &input = Application::get().input();
|
||||
Vec2 screenPos = input.mouse();
|
||||
auto *input = Application::get().input();
|
||||
if (!input) return;
|
||||
Vec2 screenPos = input->mouse();
|
||||
|
||||
Vec2 worldPos = screenPos;
|
||||
if (auto *camera = scene.getActiveCamera()) {
|
||||
|
|
@ -784,13 +786,13 @@ void SceneManager::dispatchPointerEvents(Scene &scene) {
|
|||
dispatchToNode(hoverTarget_, evt);
|
||||
}
|
||||
|
||||
float scrollDelta = input.scrollDelta();
|
||||
float scrollDelta = input->scrollDelta();
|
||||
if (hoverTarget_ && scrollDelta != 0.0f) {
|
||||
Event evt = Event::createMouseScroll(Vec2(0.0f, scrollDelta), worldPos);
|
||||
dispatchToNode(hoverTarget_, evt);
|
||||
}
|
||||
|
||||
if (input.pressed(Mouse::Left)) {
|
||||
if (input->pressed(Mouse::Left)) {
|
||||
captureTarget_ = hoverTarget_;
|
||||
if (captureTarget_) {
|
||||
Event evt =
|
||||
|
|
@ -804,7 +806,7 @@ void SceneManager::dispatchPointerEvents(Scene &scene) {
|
|||
}
|
||||
}
|
||||
|
||||
if (input.released(Mouse::Left)) {
|
||||
if (input->released(Mouse::Left)) {
|
||||
Node *target = captureTarget_ ? captureTarget_ : hoverTarget_;
|
||||
if (target) {
|
||||
Event evt =
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/graphics/render_command.h>
|
||||
#include <extra2d/graphics/core/render_backend.h>
|
||||
#include <extra2d/graphics/core/render_command.h>
|
||||
#include <extra2d/scene/shape_node.h>
|
||||
#include <limits>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/graphics/render_command.h>
|
||||
#include <extra2d/graphics/texture.h>
|
||||
#include <extra2d/graphics/core/render_backend.h>
|
||||
#include <extra2d/graphics/core/render_command.h>
|
||||
#include <extra2d/graphics/texture/texture.h>
|
||||
#include <extra2d/scene/sprite.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
#include <extra2d/scene/transition_box_scene.h>
|
||||
#include <extra2d/app/application.h>
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/graphics/core/render_backend.h>
|
||||
#include <extra2d/platform/iwindow.h>
|
||||
#include <algorithm>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
|
|
@ -38,6 +39,14 @@ Ptr<TransitionBoxScene> TransitionBoxScene::create(float duration,
|
|||
void TransitionBoxScene::onTransitionStart() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新过渡进度
|
||||
* @param dt 帧间隔时间(秒)
|
||||
*/
|
||||
void TransitionBoxScene::updateTransition(float dt) {
|
||||
TransitionScene::updateTransition(dt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 渲染过渡内容
|
||||
* @param renderer 渲染后端引用
|
||||
|
|
@ -46,11 +55,8 @@ void TransitionBoxScene::onTransitionStart() {
|
|||
*/
|
||||
void TransitionBoxScene::renderContent(RenderBackend &renderer) {
|
||||
auto &app = Application::get();
|
||||
float windowWidth = static_cast<float>(app.window().width());
|
||||
float windowHeight = static_cast<float>(app.window().height());
|
||||
|
||||
elapsed_ += 1.0f / 60.0f;
|
||||
progress_ = duration_ > 0.0f ? std::min(1.0f, elapsed_ / duration_) : 1.0f;
|
||||
float windowWidth = static_cast<float>(app.window()->width());
|
||||
float windowHeight = static_cast<float>(app.window()->height());
|
||||
|
||||
if (inScene_) {
|
||||
inScene_->renderContent(renderer);
|
||||
|
|
@ -78,10 +84,6 @@ void TransitionBoxScene::renderContent(RenderBackend &renderer) {
|
|||
renderer.fillRect(Rect(x * cellW, y * cellH, cellW + 1.0f, cellH + 1.0f),
|
||||
Colors::Black);
|
||||
}
|
||||
|
||||
if (progress_ >= 1.0f && !isFinished_) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include <extra2d/scene/transition_fade_scene.h>
|
||||
#include <extra2d/app/application.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/graphics/core/render_backend.h>
|
||||
#include <extra2d/platform/iwindow.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <algorithm>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
|
@ -39,30 +40,29 @@ void TransitionFadeScene::onTransitionStart() {
|
|||
E2D_LOG_DEBUG("TransitionFadeScene::onTransitionStart - 启动淡入淡出过渡");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 渲染过渡内容
|
||||
* @param renderer 渲染后端引用
|
||||
*
|
||||
* 根据进度渲染新旧场景,并绘制遮罩层
|
||||
*/
|
||||
void TransitionFadeScene::renderContent(RenderBackend &renderer) {
|
||||
auto &app = Application::get();
|
||||
float windowWidth = static_cast<float>(app.window().width());
|
||||
float windowHeight = static_cast<float>(app.window().height());
|
||||
|
||||
elapsed_ += 1.0f / 60.0f;
|
||||
progress_ = duration_ > 0.0f ? std::min(1.0f, elapsed_ / duration_) : 1.0f;
|
||||
void TransitionFadeScene::updateTransition(float dt) {
|
||||
// 调用基类更新进度
|
||||
TransitionScene::updateTransition(dt);
|
||||
|
||||
// 检查是否需要切换场景显示
|
||||
if (!hasSwitched_ && progress_ >= 0.5f) {
|
||||
hideOutShowIn();
|
||||
}
|
||||
}
|
||||
|
||||
void TransitionFadeScene::renderContent(RenderBackend &renderer) {
|
||||
auto &app = Application::get();
|
||||
float windowWidth = static_cast<float>(app.window()->width());
|
||||
float windowHeight = static_cast<float>(app.window()->height());
|
||||
|
||||
// 根据进度选择渲染哪个场景
|
||||
if (progress_ < 0.5f) {
|
||||
drawOutScene(renderer);
|
||||
} else {
|
||||
drawInScene(renderer);
|
||||
}
|
||||
|
||||
// 计算遮罩透明度
|
||||
float maskAlpha;
|
||||
if (progress_ < 0.5f) {
|
||||
maskAlpha = progress_ * 2.0f;
|
||||
|
|
@ -80,10 +80,6 @@ void TransitionFadeScene::renderContent(RenderBackend &renderer) {
|
|||
Color maskColor = maskColor_;
|
||||
maskColor.a = maskAlpha;
|
||||
renderer.fillRect(Rect(0.0f, 0.0f, windowWidth, windowHeight), maskColor);
|
||||
|
||||
if (progress_ >= 1.0f && !isFinished_) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#include <extra2d/scene/transition_flip_scene.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/graphics/camera.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/graphics/camera/camera.h>
|
||||
#include <extra2d/graphics/core/render_backend.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
|
|
@ -36,6 +36,14 @@ Ptr<TransitionFlipScene> TransitionFlipScene::create(float duration,
|
|||
void TransitionFlipScene::onTransitionStart() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新过渡进度
|
||||
* @param dt 帧间隔时间(秒)
|
||||
*/
|
||||
void TransitionFlipScene::updateTransition(float dt) {
|
||||
TransitionScene::updateTransition(dt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 渲染过渡内容
|
||||
* @param renderer 渲染后端引用
|
||||
|
|
@ -43,9 +51,6 @@ void TransitionFlipScene::onTransitionStart() {
|
|||
* 根据进度控制新旧场景的翻转角度
|
||||
*/
|
||||
void TransitionFlipScene::renderContent(RenderBackend &renderer) {
|
||||
elapsed_ += 1.0f / 60.0f;
|
||||
progress_ = duration_ > 0.0f ? std::min(1.0f, elapsed_ / duration_) : 1.0f;
|
||||
|
||||
float easeProgress = progress_ < 0.5f ? 2.0f * progress_ * progress_
|
||||
: -1.0f + (4.0f - 2.0f * progress_) * progress_;
|
||||
|
||||
|
|
@ -99,9 +104,6 @@ void TransitionFlipScene::renderContent(RenderBackend &renderer) {
|
|||
}
|
||||
}
|
||||
|
||||
if (progress_ >= 1.0f && !isFinished_) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#include <extra2d/scene/transition_scale_scene.h>
|
||||
#include <extra2d/graphics/camera.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/graphics/camera/camera.h>
|
||||
#include <extra2d/graphics/core/render_backend.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace extra2d {
|
||||
|
|
@ -32,6 +32,14 @@ Ptr<TransitionScaleScene> TransitionScaleScene::create(float duration,
|
|||
void TransitionScaleScene::onTransitionStart() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新过渡进度
|
||||
* @param dt 帧间隔时间(秒)
|
||||
*/
|
||||
void TransitionScaleScene::updateTransition(float dt) {
|
||||
TransitionScene::updateTransition(dt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 渲染过渡内容
|
||||
* @param renderer 渲染后端引用
|
||||
|
|
@ -39,9 +47,6 @@ void TransitionScaleScene::onTransitionStart() {
|
|||
* 根据进度控制新旧场景的缩放比例
|
||||
*/
|
||||
void TransitionScaleScene::renderContent(RenderBackend &renderer) {
|
||||
elapsed_ += 1.0f / 60.0f;
|
||||
progress_ = duration_ > 0.0f ? std::min(1.0f, elapsed_ / duration_) : 1.0f;
|
||||
|
||||
float easeProgress = progress_ < 0.5f ? 2.0f * progress_ * progress_
|
||||
: -1.0f + (4.0f - 2.0f * progress_) * progress_;
|
||||
|
||||
|
|
@ -79,9 +84,6 @@ void TransitionScaleScene::renderContent(RenderBackend &renderer) {
|
|||
}
|
||||
}
|
||||
|
||||
if (progress_ >= 1.0f && !isFinished_) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#include <extra2d/scene/transition_scene.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/graphics/core/render_backend.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
|
@ -50,13 +50,32 @@ void TransitionScene::onExit() {
|
|||
Scene::onExit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新过渡进度(基类默认实现)
|
||||
* @param dt 帧间隔时间(秒)
|
||||
*
|
||||
* 子类应重写此方法更新动画进度,默认实现简单计时
|
||||
*/
|
||||
void TransitionScene::updateTransition(float dt) {
|
||||
if (isFinished_ || isCancelled_) {
|
||||
return;
|
||||
}
|
||||
|
||||
elapsed_ += dt;
|
||||
progress_ = duration_ > 0.0f ? std::min(1.0f, elapsed_ / duration_) : 1.0f;
|
||||
|
||||
if (progress_ >= 1.0f) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 完成过渡
|
||||
*
|
||||
* 标记过渡完成并调用完成回调
|
||||
*/
|
||||
void TransitionScene::finish() {
|
||||
if (isFinished_) {
|
||||
if (isFinished_ || isCancelled_) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -69,6 +88,30 @@ void TransitionScene::finish() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 取消过渡
|
||||
* @param immediate 是否立即完成(false则回滚到原场景)
|
||||
*/
|
||||
void TransitionScene::cancel(bool immediate) {
|
||||
if (isFinished_ || isCancelled_) {
|
||||
return;
|
||||
}
|
||||
|
||||
isCancelled_ = true;
|
||||
|
||||
if (immediate) {
|
||||
// 立即完成,切换到新场景
|
||||
E2D_LOG_DEBUG("TransitionScene::cancel - 立即完成过渡");
|
||||
finish();
|
||||
} else {
|
||||
// 回滚到原场景
|
||||
E2D_LOG_DEBUG("TransitionScene::cancel - 取消过渡,回滚到原场景");
|
||||
if (cancelCallback_) {
|
||||
cancelCallback_();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 渲染过渡内容
|
||||
* @param renderer 渲染后端引用
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#include <extra2d/scene/transition_slide_scene.h>
|
||||
#include <extra2d/graphics/camera.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/graphics/camera/camera.h>
|
||||
#include <extra2d/graphics/core/render_backend.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace extra2d {
|
||||
|
|
@ -35,6 +35,14 @@ Ptr<TransitionSlideScene> TransitionSlideScene::create(
|
|||
void TransitionSlideScene::onTransitionStart() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新过渡进度
|
||||
* @param dt 帧间隔时间(秒)
|
||||
*/
|
||||
void TransitionSlideScene::updateTransition(float dt) {
|
||||
TransitionScene::updateTransition(dt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 渲染过渡内容
|
||||
* @param renderer 渲染后端引用
|
||||
|
|
@ -59,9 +67,6 @@ void TransitionSlideScene::renderContent(RenderBackend &renderer) {
|
|||
}
|
||||
}
|
||||
|
||||
elapsed_ += 1.0f / 60.0f;
|
||||
progress_ = duration_ > 0.0f ? std::min(1.0f, elapsed_ / duration_) : 1.0f;
|
||||
|
||||
float easeProgress = progress_ < 0.5f ? 2.0f * progress_ * progress_
|
||||
: -1.0f + (4.0f - 2.0f * progress_) * progress_;
|
||||
|
||||
|
|
@ -131,9 +136,6 @@ void TransitionSlideScene::renderContent(RenderBackend &renderer) {
|
|||
}
|
||||
}
|
||||
|
||||
if (progress_ >= 1.0f && !isFinished_) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -0,0 +1,171 @@
|
|||
#include <extra2d/services/logger_service.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <cstdio>
|
||||
#include <cstdarg>
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <mutex>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ConsoleLogger 实现
|
||||
class ConsoleLogger::Impl {
|
||||
public:
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
ConsoleLogger::ConsoleLogger() : level_(LogLevel::Info), impl_(std::make_unique<Impl>()) {
|
||||
info_.name = "ConsoleLogger";
|
||||
info_.priority = ServicePriority::Core;
|
||||
}
|
||||
|
||||
ConsoleLogger::~ConsoleLogger() = default;
|
||||
|
||||
bool ConsoleLogger::initialize() {
|
||||
setState(ServiceState::Running);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConsoleLogger::shutdown() {
|
||||
setState(ServiceState::Stopped);
|
||||
}
|
||||
|
||||
void ConsoleLogger::setLevel(LogLevel level) {
|
||||
level_ = level;
|
||||
}
|
||||
|
||||
LogLevel ConsoleLogger::getLevel() const {
|
||||
return level_;
|
||||
}
|
||||
|
||||
bool ConsoleLogger::isEnabled(LogLevel level) const {
|
||||
return static_cast<int>(level) >= static_cast<int>(level_);
|
||||
}
|
||||
|
||||
void ConsoleLogger::log(LogLevel level, const char* fmt, ...) {
|
||||
if (!isEnabled(level)) return;
|
||||
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
output(level, buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::log(LogLevel level, const std::string& msg) {
|
||||
if (!isEnabled(level)) return;
|
||||
output(level, msg.c_str());
|
||||
}
|
||||
|
||||
void ConsoleLogger::trace(const char* fmt, ...) {
|
||||
if (!isEnabled(LogLevel::Trace)) return;
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
output(LogLevel::Trace, buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::debug(const char* fmt, ...) {
|
||||
if (!isEnabled(LogLevel::Debug)) return;
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
output(LogLevel::Debug, buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::info(const char* fmt, ...) {
|
||||
if (!isEnabled(LogLevel::Info)) return;
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
output(LogLevel::Info, buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::warn(const char* fmt, ...) {
|
||||
if (!isEnabled(LogLevel::Warn)) return;
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
output(LogLevel::Warn, buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::error(const char* fmt, ...) {
|
||||
if (!isEnabled(LogLevel::Error)) return;
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
output(LogLevel::Error, buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::fatal(const char* fmt, ...) {
|
||||
if (!isEnabled(LogLevel::Fatal)) return;
|
||||
char buffer[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||
va_end(args);
|
||||
output(LogLevel::Fatal, buffer);
|
||||
}
|
||||
|
||||
void ConsoleLogger::output(LogLevel level, const char* msg) {
|
||||
std::lock_guard<std::mutex> lock(impl_->mutex_);
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto time = std::chrono::system_clock::to_time_t(now);
|
||||
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now.time_since_epoch()) % 1000;
|
||||
|
||||
std::tm tm;
|
||||
#ifdef _WIN32
|
||||
localtime_s(&tm, &time);
|
||||
#else
|
||||
localtime_r(&time, &tm);
|
||||
#endif
|
||||
|
||||
const char* levelStr = getLevelString(level);
|
||||
|
||||
// 颜色代码
|
||||
const char* color = "";
|
||||
const char* reset = "\033[0m";
|
||||
|
||||
switch (level) {
|
||||
case LogLevel::Trace: color = "\033[90m"; break;
|
||||
case LogLevel::Debug: color = "\033[36m"; break;
|
||||
case LogLevel::Info: color = "\033[32m"; break;
|
||||
case LogLevel::Warn: color = "\033[33m"; break;
|
||||
case LogLevel::Error: color = "\033[31m"; break;
|
||||
case LogLevel::Fatal: color = "\033[35m"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
printf("%s[%02d:%02d:%02d.%03d] [%s] %s%s\n",
|
||||
color, tm.tm_hour, tm.tm_min, tm.tm_sec, (int)ms.count(),
|
||||
levelStr, msg, reset);
|
||||
}
|
||||
|
||||
const char* ConsoleLogger::getLevelString(LogLevel level) {
|
||||
switch (level) {
|
||||
case LogLevel::Trace: return "TRACE";
|
||||
case LogLevel::Debug: return "DEBUG";
|
||||
case LogLevel::Info: return "INFO";
|
||||
case LogLevel::Warn: return "WARN";
|
||||
case LogLevel::Error: return "ERROR";
|
||||
case LogLevel::Fatal: return "FATAL";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
#include <extra2d/utils/logger.h>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef __SWITCH__
|
||||
#include <switch.h>
|
||||
#endif
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
LogLevel Logger::level_ = LogLevel::Info;
|
||||
bool Logger::initialized_ = false;
|
||||
bool Logger::consoleOutput_ = true;
|
||||
bool Logger::fileOutput_ = false;
|
||||
std::string Logger::logFile_;
|
||||
void *Logger::logFileHandle_ = nullptr;
|
||||
|
||||
const char *Logger::getLevelString(LogLevel level) {
|
||||
switch (level) {
|
||||
case LogLevel::Trace:
|
||||
return "TRACE";
|
||||
case LogLevel::Debug:
|
||||
return "DEBUG";
|
||||
case LogLevel::Info:
|
||||
return "INFO ";
|
||||
case LogLevel::Warn:
|
||||
return "WARN ";
|
||||
case LogLevel::Error:
|
||||
return "ERROR";
|
||||
case LogLevel::Fatal:
|
||||
return "FATAL";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::writeToConsole(LogLevel level, const char *msg) {
|
||||
const char *levelStr = getLevelString(level);
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
WORD color = 7;
|
||||
switch (level) {
|
||||
case LogLevel::Trace: color = 8; break;
|
||||
case LogLevel::Debug: color = 8; break;
|
||||
case LogLevel::Info: color = 7; break;
|
||||
case LogLevel::Warn: color = 14; break;
|
||||
case LogLevel::Error: color = 12; break;
|
||||
case LogLevel::Fatal: color = 12 | FOREGROUND_INTENSITY; break;
|
||||
default: break;
|
||||
}
|
||||
SetConsoleTextAttribute(hConsole, color);
|
||||
printf("[%s] %s\n", levelStr, msg);
|
||||
SetConsoleTextAttribute(hConsole, 7);
|
||||
#else
|
||||
const char *colorCode = "\033[0m";
|
||||
switch (level) {
|
||||
case LogLevel::Trace: colorCode = "\033[90m"; break;
|
||||
case LogLevel::Debug: colorCode = "\033[90m"; break;
|
||||
case LogLevel::Info: colorCode = "\033[0m"; break;
|
||||
case LogLevel::Warn: colorCode = "\033[33m"; break;
|
||||
case LogLevel::Error: colorCode = "\033[31m"; break;
|
||||
case LogLevel::Fatal: colorCode = "\033[1;31m"; break;
|
||||
default: break;
|
||||
}
|
||||
printf("%s[%s] %s\033[0m\n", colorCode, levelStr, msg);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Logger::writeToFile(LogLevel level, const char *msg) {
|
||||
if (!logFileHandle_) return;
|
||||
|
||||
FILE *fp = static_cast<FILE *>(logFileHandle_);
|
||||
|
||||
time_t now = time(nullptr);
|
||||
struct tm *tm_info = localtime(&now);
|
||||
char timeBuf[32];
|
||||
strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", tm_info);
|
||||
|
||||
fprintf(fp, "[%s] [%s] %s\n", timeBuf, getLevelString(level), msg);
|
||||
fflush(fp);
|
||||
}
|
||||
|
||||
void Logger::outputLog(LogLevel level, const char *msg) {
|
||||
if (consoleOutput_) {
|
||||
writeToConsole(level, msg);
|
||||
}
|
||||
if (fileOutput_) {
|
||||
writeToFile(level, msg);
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::init() {
|
||||
if (initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
if (hOut != INVALID_HANDLE_VALUE) {
|
||||
DWORD mode = 0;
|
||||
if (GetConsoleMode(hOut, &mode)) {
|
||||
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
SetConsoleMode(hOut, mode);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __SWITCH__
|
||||
consoleInit(NULL);
|
||||
#endif
|
||||
|
||||
initialized_ = true;
|
||||
log(LogLevel::Info, "Logger initialized");
|
||||
}
|
||||
|
||||
void Logger::shutdown() {
|
||||
if (initialized_) {
|
||||
log(LogLevel::Info, "Logger shutting down");
|
||||
}
|
||||
|
||||
if (logFileHandle_) {
|
||||
fclose(static_cast<FILE *>(logFileHandle_));
|
||||
logFileHandle_ = nullptr;
|
||||
}
|
||||
|
||||
#ifdef __SWITCH__
|
||||
consoleExit(NULL);
|
||||
#endif
|
||||
|
||||
initialized_ = false;
|
||||
fileOutput_ = false;
|
||||
}
|
||||
|
||||
void Logger::setLevel(LogLevel level) {
|
||||
level_ = level;
|
||||
}
|
||||
|
||||
void Logger::setConsoleOutput(bool enable) {
|
||||
consoleOutput_ = enable;
|
||||
}
|
||||
|
||||
void Logger::setFileOutput(const std::string &filename) {
|
||||
if (logFileHandle_) {
|
||||
fclose(static_cast<FILE *>(logFileHandle_));
|
||||
logFileHandle_ = nullptr;
|
||||
}
|
||||
|
||||
logFile_ = filename;
|
||||
fileOutput_ = !filename.empty();
|
||||
|
||||
if (fileOutput_) {
|
||||
#ifdef _WIN32
|
||||
FILE *fp = nullptr;
|
||||
fopen_s(&fp, filename.c_str(), "a");
|
||||
#else
|
||||
FILE *fp = fopen(filename.c_str(), "a");
|
||||
#endif
|
||||
logFileHandle_ = fp;
|
||||
|
||||
if (fp) {
|
||||
time_t now = time(nullptr);
|
||||
struct tm *tm_info = localtime(&now);
|
||||
char timeBuf[32];
|
||||
strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", tm_info);
|
||||
fprintf(fp, "\n=== Log session started at %s ===\n", timeBuf);
|
||||
fflush(fp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
186
README.md
186
README.md
|
|
@ -25,7 +25,7 @@
|
|||
<i>高性能、模块化、支持 Nintendo Switch</i>
|
||||
</p>
|
||||
|
||||
[构建指南](#构建指南) | [快速开始](./docs/quick_start.md) | [示例程序](#示例程序) | [模块系统](./docs/module_system.md)
|
||||
[构建指南](#构建指南) | [快速开始](#快速开始) | [示例程序](#示例程序) | [模块系统](./docs/module_system.md)
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -39,11 +39,12 @@
|
|||
|
||||
- **跨平台支持**:Windows、Linux、macOS、Nintendo Switch
|
||||
- **模块化架构**:模块系统 + 服务系统,灵活可扩展
|
||||
- **显式注册**:通过 `Application::use()` 注册模块,参考 Kiwano 设计
|
||||
- **场景图系统**:树形节点结构,支持变换继承
|
||||
- **输入系统**:键盘、鼠标、手柄、触摸,事件驱动
|
||||
- **渲染系统**:OpenGL ES 3.2,支持自定义着色器
|
||||
- **视口适配**:多种适配模式,自动响应窗口大小变化
|
||||
- **音频系统**:高质量音频播放(规划中)
|
||||
- **UI 系统**:完整的 UI 控件支持(规划中)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -58,19 +59,18 @@ flowchart TB
|
|||
|
||||
subgraph Core["Core Layer (核心层)"]
|
||||
direction LR
|
||||
MOD[Module<br/>模块基类]
|
||||
MR[ModuleRegistry<br/>模块注册表]
|
||||
SL[ServiceLocator<br/>服务定位器]
|
||||
end
|
||||
|
||||
subgraph Modules["Modules (模块系统)"]
|
||||
direction TB
|
||||
subgraph CoreModules["Core Modules"]
|
||||
LOGGER[Logger Module<br/>日志系统]
|
||||
CONFIG[Config Module<br/>配置管理]
|
||||
PLATFORM[Platform Module<br/>平台检测]
|
||||
WINDOW[Window Module<br/>窗口管理]
|
||||
end
|
||||
subgraph FeatureModules["Feature Modules"]
|
||||
subgraph OtherModules["Feature Modules"]
|
||||
INPUT[Input Module<br/>输入处理]
|
||||
RENDER[Render Module<br/>渲染系统]
|
||||
end
|
||||
|
|
@ -106,9 +106,9 @@ flowchart TB
|
|||
INPUT_SYS[Input System<br/>输入系统]
|
||||
end
|
||||
|
||||
APP --> MOD
|
||||
APP --> MR
|
||||
APP --> SL
|
||||
MOD --> Modules
|
||||
MR --> Modules
|
||||
SL --> Services
|
||||
SCENE_SVC --> SCENE
|
||||
SCENE --> NODE
|
||||
|
|
@ -127,36 +127,11 @@ flowchart TB
|
|||
|
||||
| 模块 | 职责 | 优先级 |
|
||||
|-----|------|-------|
|
||||
| Logger | 日志系统 | -1 |
|
||||
| Config | 配置管理 | 0 |
|
||||
| Platform | 平台检测 | 10 |
|
||||
| Window | 窗口管理 | 20 |
|
||||
| Input | 输入处理 | 30 |
|
||||
| Render | 渲染系统 | 40 |
|
||||
|
||||
### 模块自发现原理
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as 编译时
|
||||
participant R as 运行时
|
||||
participant MR as ModuleRegistry
|
||||
participant App as Application
|
||||
|
||||
Note over C: E2D_MODULE 宏展开
|
||||
C->>C: 创建静态变量 ModuleAutoRegister<T>
|
||||
C->>C: 添加 __attribute__((used)) 防止优化
|
||||
|
||||
Note over R: 程序启动
|
||||
R->>MR: 静态初始化阶段
|
||||
MR->>MR: 构造函数自动调用 registerModule()
|
||||
MR->>MR: 模块信息存入注册表
|
||||
|
||||
Note over App: Application::init()
|
||||
App->>MR: getModules()
|
||||
MR->>App: 返回按优先级排序的模块列表
|
||||
App->>App: 按顺序调用 setupModule()
|
||||
```
|
||||
| Config | 配置管理 | Core (0) |
|
||||
| Platform | 平台检测 | Core (0) |
|
||||
| Window | 窗口管理 | Core (0) |
|
||||
| Input | 输入处理 | Input (50) |
|
||||
| Render | 渲染系统 | Graphics (100) |
|
||||
|
||||
### 服务系统
|
||||
|
||||
|
|
@ -238,10 +213,8 @@ int main() {
|
|||
config.appName = "My Game";
|
||||
config.appVersion = "1.0.0";
|
||||
|
||||
// 获取应用实例
|
||||
Application& app = Application::get();
|
||||
|
||||
// 初始化
|
||||
Application& app = Application::get();
|
||||
if (!app.init(config)) {
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -271,46 +244,6 @@ int main() {
|
|||
}
|
||||
```
|
||||
|
||||
### 创建自定义模块
|
||||
|
||||
```cpp
|
||||
#include <extra2d/core/module.h>
|
||||
|
||||
class MyModule : public extra2d::Module {
|
||||
public:
|
||||
const char* getName() const override { return "MyModule"; }
|
||||
|
||||
int getPriority() const override { return 1000; }
|
||||
|
||||
void setupModule() override {
|
||||
// 初始化资源
|
||||
extra2d::E2D_LOG_INFO("MyModule initialized");
|
||||
}
|
||||
|
||||
void destroyModule() override {
|
||||
// 清理资源
|
||||
extra2d::E2D_LOG_INFO("MyModule destroyed");
|
||||
}
|
||||
|
||||
void onUpdate(extra2d::UpdateContext& ctx) override {
|
||||
// 更新逻辑
|
||||
ctx.next(); // 继续下一个模块
|
||||
}
|
||||
};
|
||||
|
||||
// 在 main 中注册
|
||||
int main() {
|
||||
auto& app = extra2d::Application::get();
|
||||
|
||||
MyModule myModule;
|
||||
app.use(myModule); // 显式注册
|
||||
|
||||
app.init();
|
||||
app.run();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 场景图示例
|
||||
|
||||
```cpp
|
||||
|
|
@ -340,22 +273,22 @@ root->addChild(child);
|
|||
auto eventService = app.events();
|
||||
|
||||
// 键盘事件
|
||||
eventService->addListener(EventType::KeyPress, [](Event& e) {
|
||||
eventService->addListener(EventType::KeyPressed, [](Event& e) {
|
||||
auto& key = std::get<KeyEvent>(e.data);
|
||||
if (key.scancode == static_cast<int>(Key::Escape)) {
|
||||
if (key.keyCode == static_cast<int>(Key::Escape)) {
|
||||
Application::get().quit();
|
||||
}
|
||||
});
|
||||
|
||||
// 鼠标事件
|
||||
eventService->addListener(EventType::MousePress, [](Event& e) {
|
||||
auto& mouse = std::get<MouseEvent>(e.data);
|
||||
eventService->addListener(EventType::MouseButtonPressed, [](Event& e) {
|
||||
auto& mouse = std::get<MouseButtonEvent>(e.data);
|
||||
// 处理鼠标点击
|
||||
});
|
||||
|
||||
// 手柄事件
|
||||
eventService->addListener(EventType::GamepadPress, [](Event& e) {
|
||||
auto& gamepad = std::get<GamepadEvent>(e.data);
|
||||
eventService->addListener(EventType::GamepadButtonPressed, [](Event& e) {
|
||||
auto& gamepad = std::get<GamepadButtonEvent>(e.data);
|
||||
// 处理手柄输入
|
||||
});
|
||||
```
|
||||
|
|
@ -367,13 +300,11 @@ eventService->addListener(EventType::GamepadPress, [](Event& e) {
|
|||
| 示例 | 说明 |
|
||||
|-----|------|
|
||||
| `demo_basic` | 基础示例:场景图、输入事件、视口适配 |
|
||||
| `demo_hello_module` | 自定义模块示例:展示如何创建和注册模块 |
|
||||
|
||||
运行示例:
|
||||
|
||||
```bash
|
||||
xmake run demo_basic
|
||||
xmake run demo_hello_module
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -395,7 +326,6 @@ xmake run demo_hello_module
|
|||
|
||||
## 文档
|
||||
|
||||
- [快速入门指南](./docs/quick_start.md) - 从安装到创建第一个游戏
|
||||
- [模块系统文档](./docs/module_system.md) - 模块系统、服务系统、场景图、视口适配
|
||||
|
||||
---
|
||||
|
|
@ -409,36 +339,98 @@ Extra2D/
|
|||
│ │ ├── KHR/ # KHR 平台头文件
|
||||
│ │ ├── extra2d/ # 引擎公共头文件
|
||||
│ │ │ ├── app/ # 应用程序
|
||||
│ │ │ ├── audio/ # 音频配置
|
||||
│ │ │ ├── config/ # 配置系统
|
||||
│ │ │ ├── core/ # 核心类型、模块基类
|
||||
│ │ │ ├── core/ # 核心类型
|
||||
│ │ │ ├── event/ # 事件系统
|
||||
│ │ │ ├── graphics/ # 图形渲染
|
||||
│ │ │ ├── input/ # 输入配置
|
||||
│ │ │ ├── modules/ # 内置模块
|
||||
│ │ │ │ ├── core/ # 渲染核心
|
||||
│ │ │ │ ├── camera/ # 相机和视口
|
||||
│ │ │ │ ├── shader/ # Shader 系统
|
||||
│ │ │ │ ├── texture/ # 纹理系统
|
||||
│ │ │ │ ├── memory/ # GPU 内存管理
|
||||
│ │ │ │ └── opengl/ # OpenGL 实现
|
||||
│ │ │ ├── platform/ # 平台抽象
|
||||
│ │ │ ├── scene/ # 场景系统
|
||||
│ │ │ ├── script/ # 脚本系统
|
||||
│ │ │ ├── services/ # 服务接口
|
||||
│ │ │ └── utils/ # 工具库
|
||||
│ │ ├── glad/ # OpenGL 加载器
|
||||
│ │ └── stb/ # STB 单文件库
|
||||
│ ├── shaders/ # 着色器文件
|
||||
│ │ ├── builtin/ # 内置着色器
|
||||
│ │ ├── common/ # 公共着色器代码
|
||||
│ │ └── effects/ # 特效着色器
|
||||
│ └── src/ # 源文件
|
||||
│ ├── app/ # 应用实现
|
||||
│ ├── config/ # 配置实现
|
||||
│ ├── core/ # 核心实现
|
||||
│ ├── event/ # 事件实现
|
||||
│ ├── glad/ # GLAD 实现
|
||||
│ ├── graphics/ # 图形实现
|
||||
│ │ ├── core/ # 渲染核心
|
||||
│ │ ├── camera/ # 相机和视口
|
||||
│ │ ├── shader/ # Shader 系统
|
||||
│ │ ├── texture/ # 纹理系统
|
||||
│ │ ├── memory/ # GPU 内存管理
|
||||
│ │ └── opengl/ # OpenGL 实现
|
||||
│ ├── platform/ # 平台实现
|
||||
│ │ └── backends/ # 后端实现
|
||||
│ │ └── sdl2/ # SDL2 后端
|
||||
│ ├── scene/ # 场景实现
|
||||
│ ├── services/ # 服务实现
|
||||
│ └── utils/ # 工具实现
|
||||
├── docs/ # 文档
|
||||
├── examples/ # 示例程序
|
||||
│ └── basic/ # 基础示例
|
||||
└── xmake/ # 构建配置
|
||||
└── toolchains/ # 工具链配置
|
||||
```
|
||||
Extra2D/
|
||||
├── Extra2D/
|
||||
│ ├── include/
|
||||
│ │ ├── KHR/ # KHR 平台头文件
|
||||
│ │ ├── extra2d/ # 引擎公共头文件
|
||||
│ │ │ ├── app/ # 应用程序
|
||||
│ │ │ ├── audio/ # 音频配置
|
||||
│ │ │ ├── config/ # 配置系统
|
||||
│ │ │ ├── core/ # 核心类型
|
||||
│ │ │ ├── debug/ # 调试配置
|
||||
│ │ │ ├── event/ # 事件系统
|
||||
│ │ │ ├── graphics/ # 图形渲染
|
||||
│ │ │ │ └── opengl/ # OpenGL 实现
|
||||
│ │ │ ├── input/ # 输入配置
|
||||
│ │ │ ├── platform/ # 平台抽象
|
||||
│ │ │ ├── resource/ # 资源配置
|
||||
│ │ │ ├── scene/ # 场景系统
|
||||
│ │ │ ├── services/ # 服务接口
|
||||
│ │ │ └── utils/ # 工具库
|
||||
│ │ ├── glad/ # OpenGL 加载器
|
||||
│ │ └── stb/ # STB 单文件库
|
||||
│ ├── shaders/ # 着色器文件
|
||||
│ │ ├── builtin/ # 内置着色器
|
||||
│ │ ├── common/ # 公共着色器代码
|
||||
│ │ └── effects/ # 特效着色器
|
||||
│ └── src/ # 源文件
|
||||
│ ├── app/ # 应用实现
|
||||
│ ├── config/ # 配置实现
|
||||
│ ├── core/ # 核心实现
|
||||
│ ├── debug/ # 调试实现
|
||||
│ ├── event/ # 事件实现
|
||||
│ ├── glad/ # GLAD 实现
|
||||
│ ├── graphics/ # 图形实现
|
||||
│ ├── modules/ # 模块实现
|
||||
│ │ └── opengl/ # OpenGL 实现
|
||||
│ ├── platform/ # 平台实现
|
||||
│ │ └── backends/ # 后端实现
|
||||
│ │ └── sdl2/ # SDL2 后端
|
||||
│ ├── resource/ # 资源实现
|
||||
│ ├── scene/ # 场景实现
|
||||
│ ├── script/ # 脚本实现
|
||||
│ ├── services/ # 服务实现
|
||||
│ └── utils/ # 工具实现
|
||||
├── docs/ # 文档
|
||||
├── examples/ # 示例程序
|
||||
│ ├── basic/ # 基础示例
|
||||
│ └── hello_module/ # 自定义模块示例
|
||||
│ └── basic/ # 基础示例
|
||||
└── xmake/ # 构建配置
|
||||
└── toolchains/ # 工具链配置
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,24 +1,19 @@
|
|||
/**
|
||||
* @file main.cpp
|
||||
* @brief Extra2D 场景图测试示例
|
||||
*
|
||||
* 演示场景图功能:
|
||||
* - 节点层级关系
|
||||
* - 变换(位置、旋转、缩放)
|
||||
* - 形状节点渲染
|
||||
* - 输入事件处理
|
||||
*
|
||||
* 模块现在通过 E2D_MODULE 宏自动注册,无需手动调用 app.use()
|
||||
*/
|
||||
|
||||
#include <extra2d/extra2d.h>
|
||||
#include <extra2d/platform/window_module.h>
|
||||
#include <extra2d/platform/input_module.h>
|
||||
#include <extra2d/graphics/core/render_module.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/services/event_service.h>
|
||||
#include <extra2d/services/camera_service.h>
|
||||
#include <iostream>
|
||||
|
||||
using namespace extra2d;
|
||||
|
||||
/**
|
||||
* @brief 创建场景图测试
|
||||
*/
|
||||
void createSceneGraph(Scene *scene) {
|
||||
float width = scene->getWidth();
|
||||
float height = scene->getHeight();
|
||||
|
|
@ -87,7 +82,7 @@ void createSceneGraph(Scene *scene) {
|
|||
std::cout << " └── Root (center)" << std::endl;
|
||||
std::cout << " ├── Parent1 (left)" << std::endl;
|
||||
std::cout << " │ ├── RedRect (100x100)" << std::endl;
|
||||
std::cout << " │ └── Child1 (rotated 45°, scaled 0.5)" << std::endl;
|
||||
std::cout << " │ └── Child1 (rotated 45, scaled 0.5)" << std::endl;
|
||||
std::cout << " │ └── OrangeRect (60x60)" << std::endl;
|
||||
std::cout << " ├── Parent2 (right)" << std::endl;
|
||||
std::cout << " │ ├── BlueCircle (radius 60)" << std::endl;
|
||||
|
|
@ -98,45 +93,54 @@ void createSceneGraph(Scene *scene) {
|
|||
std::cout << "=============================\n" << std::endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 主函数
|
||||
*/
|
||||
int main(int argc, char *argv[]) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
std::cout << "Extra2D Scene Graph Demo - Starting..." << std::endl;
|
||||
|
||||
AppConfig config = AppConfig::createDefault();
|
||||
config.appName = "Extra2D Scene Graph Demo";
|
||||
config.appVersion = "1.0.0";
|
||||
|
||||
Application &app = Application::get();
|
||||
|
||||
// 模块已通过 E2D_MODULE 宏自动注册,无需调用 app.use()
|
||||
// 注册模块(按优先级顺序)
|
||||
WindowModule::Cfg winCfg;
|
||||
winCfg.w = 1280;
|
||||
winCfg.h = 720;
|
||||
winCfg.priority = 0;
|
||||
app.use<WindowModule>(winCfg);
|
||||
|
||||
if (!app.init(config)) {
|
||||
RenderModule::Cfg renderCfg;
|
||||
renderCfg.priority = 10;
|
||||
app.use<RenderModule>(renderCfg);
|
||||
|
||||
InputModule::Cfg inputCfg;
|
||||
inputCfg.priority = 20;
|
||||
app.use<InputModule>(inputCfg);
|
||||
|
||||
std::cout << "Initializing application..." << std::endl;
|
||||
if (!app.init()) {
|
||||
std::cerr << "Failed to initialize application!" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::cout << "Application initialized successfully!" << std::endl;
|
||||
std::cout << "Window: " << app.window().width() << "x"
|
||||
<< app.window().height() << std::endl;
|
||||
|
||||
auto* win = app.window();
|
||||
if (win) {
|
||||
std::cout << "Window: " << win->width() << "x" << win->height() << std::endl;
|
||||
}
|
||||
|
||||
auto eventService = app.events();
|
||||
auto eventService = ServiceLocator::instance().getService<IEventService>();
|
||||
if (eventService) {
|
||||
eventService->addListener(EventType::KeyPress, [](Event &e) {
|
||||
eventService->addListener(EventType::KeyPressed, [](Event &e) {
|
||||
auto &keyEvent = std::get<KeyEvent>(e.data);
|
||||
|
||||
if (keyEvent.scancode == static_cast<int>(Key::Escape)) {
|
||||
if (keyEvent.keyCode == static_cast<int>(Key::Escape)) {
|
||||
e.handled = true;
|
||||
Application::get().quit();
|
||||
}
|
||||
});
|
||||
|
||||
eventService->addListener(EventType::MousePress, [](Event &e) {
|
||||
auto &mouseEvent = std::get<MouseEvent>(e.data);
|
||||
eventService->addListener(EventType::MouseButtonPressed, [](Event &e) {
|
||||
auto &mouseEvent = std::get<MouseButtonEvent>(e.data);
|
||||
std::cout << "[Click] Button " << mouseEvent.button << " at ("
|
||||
<< mouseEvent.position.x << ", " << mouseEvent.position.y << ")"
|
||||
<< std::endl;
|
||||
|
|
@ -145,22 +149,23 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
auto scene = Scene::create();
|
||||
scene->setBackgroundColor(Color(0.12f, 0.12f, 0.16f, 1.0f));
|
||||
scene->setViewportSize(static_cast<float>(app.window().width()),
|
||||
static_cast<float>(app.window().height()));
|
||||
if (win) {
|
||||
scene->setViewportSize(static_cast<float>(win->width()),
|
||||
static_cast<float>(win->height()));
|
||||
}
|
||||
|
||||
auto cameraService = app.camera();
|
||||
if (cameraService) {
|
||||
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
|
||||
if (cameraService && win) {
|
||||
ViewportConfig vpConfig;
|
||||
vpConfig.logicWidth = static_cast<float>(app.window().width());
|
||||
vpConfig.logicHeight = static_cast<float>(app.window().height());
|
||||
vpConfig.logicWidth = static_cast<float>(win->width());
|
||||
vpConfig.logicHeight = static_cast<float>(win->height());
|
||||
vpConfig.mode = ViewportMode::AspectRatio;
|
||||
cameraService->setViewportConfig(vpConfig);
|
||||
cameraService->updateViewport(app.window().width(), app.window().height());
|
||||
cameraService->updateViewport(win->width(), win->height());
|
||||
cameraService->applyViewportAdapter();
|
||||
}
|
||||
|
||||
createSceneGraph(scene.get());
|
||||
|
||||
app.enterScene(scene);
|
||||
|
||||
std::cout << "\nControls:" << std::endl;
|
||||
|
|
@ -170,6 +175,9 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
app.run();
|
||||
|
||||
std::cout << "Shutting down..." << std::endl;
|
||||
app.shutdown();
|
||||
|
||||
std::cout << "Goodbye!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,82 +1,38 @@
|
|||
#include "hello_module.h"
|
||||
#include <extra2d/core/module_macros.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <iostream>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
HelloModule::HelloModule()
|
||||
: Module()
|
||||
, config_()
|
||||
, time_(0.0f) {
|
||||
}
|
||||
HelloModule::HelloModule(const Cfg& cfg) : cfg_(cfg) {}
|
||||
|
||||
HelloModule::~HelloModule() {
|
||||
if (isInitialized()) {
|
||||
destroyModule();
|
||||
if (initialized_) {
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void HelloModule::setupModule() {
|
||||
if (isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config_.greeting.empty()) {
|
||||
config_.greeting = "Hello, Extra2D!";
|
||||
}
|
||||
|
||||
if (config_.repeatCount <= 0) {
|
||||
config_.repeatCount = 1;
|
||||
}
|
||||
|
||||
setInitialized(true);
|
||||
bool HelloModule::init() {
|
||||
if (initialized_) return true;
|
||||
|
||||
E2D_LOG_INFO("HelloModule initialized");
|
||||
E2D_LOG_INFO(" Greeting: {}", config_.greeting);
|
||||
E2D_LOG_INFO(" Repeat Count: {}", config_.repeatCount);
|
||||
E2D_LOG_INFO(" Logging Enabled: {}", config_.enableLogging);
|
||||
|
||||
sayHello();
|
||||
std::cout << "HelloModule initialized" << std::endl;
|
||||
std::cout << " Greeting: " << cfg_.greeting << std::endl;
|
||||
std::cout << " Repeat count: " << cfg_.repeatCount << std::endl;
|
||||
|
||||
initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HelloModule::destroyModule() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config_.enableLogging) {
|
||||
E2D_LOG_INFO("HelloModule shutdown - Goodbye!");
|
||||
}
|
||||
|
||||
setInitialized(false);
|
||||
}
|
||||
|
||||
void HelloModule::onUpdate(UpdateContext& ctx) {
|
||||
if (!isInitialized()) {
|
||||
ctx.next();
|
||||
return;
|
||||
}
|
||||
|
||||
time_ += ctx.getDeltaTime();
|
||||
|
||||
if (time_ >= 5.0f) {
|
||||
sayHello();
|
||||
time_ = 0.0f;
|
||||
}
|
||||
|
||||
ctx.next();
|
||||
void HelloModule::shutdown() {
|
||||
if (!initialized_) return;
|
||||
|
||||
std::cout << "HelloModule shutdown" << std::endl;
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
void HelloModule::sayHello() const {
|
||||
if (!config_.enableLogging) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < config_.repeatCount; ++i) {
|
||||
E2D_LOG_INFO("[HelloModule] {}", config_.greeting);
|
||||
for (int i = 0; i < cfg_.repeatCount; ++i) {
|
||||
std::cout << cfg_.greeting << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
E2D_MODULE(HelloModule, 1000)
|
||||
|
|
|
|||
|
|
@ -1,78 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/export.h>
|
||||
#include <extra2d/core/module.h>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief Hello模块配置数据结构
|
||||
*/
|
||||
struct HelloModuleConfigData {
|
||||
std::string greeting = "Hello, Extra2D!";
|
||||
int repeatCount = 1;
|
||||
bool enableLogging = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Hello模块
|
||||
*
|
||||
* 这是一个简单的自定义模块示例,展示如何:
|
||||
* 1. 继承 Module 基类
|
||||
* 2. 实现生命周期方法
|
||||
* 3. 使用 E2D_MODULE 宏自动注册模块
|
||||
* @brief Hello模块示例
|
||||
* 展示如何创建自定义模块
|
||||
*/
|
||||
class HelloModule : public Module {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*/
|
||||
HelloModule();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~HelloModule() override;
|
||||
|
||||
/**
|
||||
* @brief 获取模块名称
|
||||
*/
|
||||
const char *getName() const override { return "HelloModule"; }
|
||||
|
||||
/**
|
||||
* @brief 获取模块优先级
|
||||
*/
|
||||
int getPriority() const override { return 1000; }
|
||||
|
||||
/**
|
||||
* @brief 设置模块
|
||||
*/
|
||||
void setupModule() override;
|
||||
|
||||
/**
|
||||
* @brief 销毁模块
|
||||
*/
|
||||
void destroyModule() override;
|
||||
|
||||
/**
|
||||
* @brief 更新时
|
||||
*/
|
||||
void onUpdate(UpdateContext &ctx) override;
|
||||
|
||||
/**
|
||||
* @brief 设置配置
|
||||
*/
|
||||
void setConfig(const HelloModuleConfigData &config) { config_ = config; }
|
||||
|
||||
/**
|
||||
* @brief 执行问候操作
|
||||
*/
|
||||
void sayHello() const;
|
||||
/**
|
||||
* @brief 配置结构
|
||||
*/
|
||||
struct Cfg {
|
||||
std::string greeting = "Hello, Extra2D!";
|
||||
int repeatCount = 1;
|
||||
int priority = 100;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param cfg 配置
|
||||
*/
|
||||
explicit HelloModule(const Cfg& cfg = Cfg{});
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~HelloModule() override;
|
||||
|
||||
bool init() override;
|
||||
void shutdown() override;
|
||||
bool ok() const override { return initialized_; }
|
||||
const char* name() const override { return "hello"; }
|
||||
int priority() const override { return cfg_.priority; }
|
||||
|
||||
/**
|
||||
* @brief 执行问候操作
|
||||
*/
|
||||
void sayHello() const;
|
||||
|
||||
private:
|
||||
HelloModuleConfigData config_;
|
||||
float time_ = 0.0f;
|
||||
Cfg cfg_;
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,61 +1,82 @@
|
|||
#include "hello_module.h"
|
||||
#include <extra2d/extra2d.h>
|
||||
#include <extra2d/app/application.h>
|
||||
#include <extra2d/platform/window_module.h>
|
||||
#include <extra2d/graphics/core/render_module.h>
|
||||
#include <extra2d/scene/scene.h>
|
||||
#include <extra2d/services/scene_service.h>
|
||||
#include <iostream>
|
||||
|
||||
using namespace extra2d;
|
||||
|
||||
/**
|
||||
* @brief 自定义场景类
|
||||
*
|
||||
* 展示如何在场景中使用自定义模块
|
||||
*/
|
||||
class HelloScene : public Scene {
|
||||
public:
|
||||
static Ptr<HelloScene> create() { return makeShared<HelloScene>(); }
|
||||
|
||||
void onEnter() override {
|
||||
Scene::onEnter();
|
||||
addListener(EventType::KeyPress, [](Event &e) {
|
||||
auto &keyEvent = std::get<KeyEvent>(e.data);
|
||||
auto app = Application::get().getModule<HelloModule>();
|
||||
E2D_LOG_INFO("Module {}", app->getName());
|
||||
if (keyEvent.scancode == static_cast<int>(Key::Escape)) {
|
||||
e.handled = true;
|
||||
E2D_LOG_INFO("ESC pressed, exiting...");
|
||||
Application::get().quit();
|
||||
}
|
||||
});
|
||||
E2D_LOG_INFO("HelloScene entered");
|
||||
|
||||
std::cout << "HelloScene entered" << std::endl;
|
||||
setBackgroundColor(Color(0.1f, 0.1f, 0.2f, 1.0f));
|
||||
|
||||
auto* hello = Application::get().get<HelloModule>();
|
||||
if (hello) {
|
||||
std::cout << "Scene calling HelloModule from onEnter..." << std::endl;
|
||||
hello->sayHello();
|
||||
}
|
||||
}
|
||||
|
||||
void onUpdate(float dt) override {
|
||||
Scene::onUpdate(dt);
|
||||
time_ += dt;
|
||||
|
||||
if (time_ >= 5.0f) {
|
||||
auto* hello = Application::get().get<HelloModule>();
|
||||
if (hello) {
|
||||
std::cout << "Scene calling HelloModule from onUpdate..." << std::endl;
|
||||
hello->sayHello();
|
||||
}
|
||||
time_ = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
float time_ = 0.0f;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 应用程序入口
|
||||
*
|
||||
* 静态链接时模块直接编译到可执行文件,自动注册,无需任何额外代码!
|
||||
*/
|
||||
int main(int argc, char *argv[]) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
std::cout << "=== Hello Module Example ===" << std::endl;
|
||||
std::cout << "This example demonstrates how to create a custom module" << std::endl;
|
||||
std::cout << "" << std::endl;
|
||||
|
||||
Application &app = Application::get();
|
||||
|
||||
AppConfig appConfig;
|
||||
appConfig.appName = "HelloModule Example";
|
||||
appConfig.appVersion = "1.0.0";
|
||||
// 注册模块
|
||||
app.use<WindowModule>(WindowModule::Cfg{.w = 800, .h = 600});
|
||||
app.use<RenderModule>();
|
||||
app.use<HelloModule>(HelloModule::Cfg{.greeting = "Hello from custom module!", .repeatCount = 3});
|
||||
|
||||
if (!app.init(appConfig)) {
|
||||
E2D_LOG_ERROR("Failed to initialize application");
|
||||
if (!app.init()) {
|
||||
std::cerr << "Failed to initialize application" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "" << std::endl;
|
||||
std::cout << "Application initialized successfully" << std::endl;
|
||||
std::cout << "" << std::endl;
|
||||
|
||||
auto scene = HelloScene::create();
|
||||
app.enterScene(scene);
|
||||
|
||||
std::cout << "Starting main loop..." << std::endl;
|
||||
std::cout << "Press ESC or close window to exit" << std::endl;
|
||||
std::cout << "" << std::endl;
|
||||
|
||||
app.run();
|
||||
|
||||
std::cout << "Application shutting down..." << std::endl;
|
||||
app.shutdown();
|
||||
std::cout << "Application shutdown complete" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue