diff --git a/Extra2D/include/extra2d/app/application.h b/Extra2D/include/extra2d/app/application.h index 6d4773e..2e11d4d 100644 --- a/Extra2D/include/extra2d/app/application.h +++ b/Extra2D/include/extra2d/app/application.h @@ -1,253 +1,143 @@ #pragma once -#include -#include -#include -#include -#include -#include #include -#include +#include +#include +#include +#include #include 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 scenes(); - - /** - * @brief 获取计时器服务 - * @return 计时器服务共享指针 - */ - SharedPtr timers(); - - /** - * @brief 获取事件服务 - * @return 事件服务共享指针 - */ - SharedPtr events(); - - /** - * @brief 获取相机服务 - * @return 相机服务共享指针 - */ - SharedPtr camera(); - - /** - * @brief 进入场景 - * @param scene 场景指针 - */ - void enterScene(Ptr 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 T *getModule() { - return ModuleRegistry::instance().getModule(); - } - - /** - * @brief 获取模块实例(按名称) - * @param name 模块名称 - * @return 模块指针,不存在返回 nullptr - */ - Module *getModule(const char *name) { - return ModuleRegistry::instance().getModule(name); - } - - /** - * @brief 注册自定义服务 - * @tparam T 服务接口类型 - * @param service 服务实例 - */ - template void registerService(SharedPtr service) { - ServiceLocator::instance().registerService(service); - } - - /** - * @brief 获取服务 - * @tparam T 服务接口类型 - * @return 服务共享指针 - */ - template SharedPtr getService() { - return ServiceLocator::instance().getService(); - } + static Application& get(); + + Application(const Application&) = delete; + Application& operator=(const Application&) = delete; + + /** + * @brief 注册模块 + * @tparam T 模块类型 + * @tparam Args 构造函数参数 + * @return 模块指针 + */ + template + T* use(Args&&... args) { + return Registry::instance().use(std::forward(args)...); + } + + /** + * @brief 获取模块 + * @tparam T 模块类型 + * @return 模块指针 + */ + template + T* get() const { + return Registry::instance().get(); + } + + /** + * @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 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 diff --git a/Extra2D/include/extra2d/config/config_loader.h b/Extra2D/include/extra2d/config/config_loader.h deleted file mode 100644 index 8d2eb7f..0000000 --- a/Extra2D/include/extra2d/config/config_loader.h +++ /dev/null @@ -1,199 +0,0 @@ -#pragma once - -#include -#include -#include - -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 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 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 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 create(const std::string& extension); - - /** - * @brief 根据文件路径创建加载器 - * @param filepath 文件路径 - * @return 配置加载器实例,如果不支持返回 nullptr - */ - static UniquePtr createForFile(const std::string& filepath); - - /** - * @brief 检查是否支持指定扩展名 - * @param extension 文件扩展名 - * @return 如果支持返回 true - */ - static bool isExtensionSupported(const std::string& extension); - - /** - * @brief 获取所有支持的扩展名 - * @return 支持的扩展名列表 - */ - static std::vector getSupportedExtensions(); -}; - -} diff --git a/Extra2D/include/extra2d/config/config_manager.h b/Extra2D/include/extra2d/config/config_manager.h deleted file mode 100644 index ee864bc..0000000 --- a/Extra2D/include/extra2d/config/config_manager.h +++ /dev/null @@ -1,280 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -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; - -/** - * @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 m_loader; - std::string m_configPath; - bool m_initialized = false; - bool m_modified = false; - mutable std::mutex m_mutex; - - std::unordered_map m_changeCallbacks; - int m_nextCallbackId = 1; - - std::unordered_map m_rawValues; - - bool m_autoSaveEnabled = false; - float m_autoSaveInterval = 30.0f; - float m_autoSaveTimer = 0.0f; -}; - -#define CONFIG_MANAGER ConfigManager::instance() - -} diff --git a/Extra2D/include/extra2d/core/module.h b/Extra2D/include/extra2d/core/module.h index 6979002..02fcc33 100644 --- a/Extra2D/include/extra2d/core/module.h +++ b/Extra2D/include/extra2d/core/module.h @@ -1,205 +1,76 @@ #pragma once -#include #include +#include #include +#include 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(modules_.size()); } - -protected: - /** - * @brief 构造函数 - * @param modules 模块列表引用 - */ - ModuleContext(std::vector& modules); - - /** - * @brief 处理模块 - * @param m 模块指针 - */ - virtual void handle(Module* m) = 0; - - std::vector& modules_; - int index_ = 0; -}; - -/** - * @brief 更新上下文 - * 用于模块更新阶段 - */ -class UpdateContext : public ModuleContext { -public: - /** - * @brief 构造函数 - * @param modules 模块列表引用 - * @param deltaTime 帧间隔时间 - */ - UpdateContext(std::vector& 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& 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 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()>; } // namespace extra2d diff --git a/Extra2D/include/extra2d/core/registry.h b/Extra2D/include/extra2d/core/registry.h new file mode 100644 index 0000000..8196e67 --- /dev/null +++ b/Extra2D/include/extra2d/core/registry.h @@ -0,0 +1,116 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +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 + T* use(Args&&... args) { + static_assert(std::is_base_of_v, "T must derive from Module"); + + auto typeIdx = std::type_index(typeid(T)); + if (modules_.count(typeIdx)) { + return static_cast(modules_[typeIdx].get()); + } + + auto module = makeUnique(std::forward(args)...); + T* ptr = module.get(); + module->setApp(app_); + modules_[typeIdx] = std::move(module); + return ptr; + } + + /** + * @brief 获取模块 + * @tparam T 模块类型 + * @return 模块指针,不存在返回 nullptr + */ + template + T* get() const { + auto typeIdx = std::type_index(typeid(T)); + auto it = modules_.find(typeIdx); + if (it != modules_.end()) { + return static_cast(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 topologicalSort(); + + std::unordered_map> modules_; + Application* app_ = nullptr; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/extra2d.h b/Extra2D/include/extra2d/extra2d.h index 5700344..b640f6b 100644 --- a/Extra2D/include/extra2d/extra2d.h +++ b/Extra2D/include/extra2d/extra2d.h @@ -6,39 +6,34 @@ // Core #include #include -#include -#include #include +#include +#include // Config #include -#include -#include - -// Modules -#include -#include -#include -#include +#include +#include // Platform #include #include #include -#include +#include +#include +#include // Graphics -#include -#include -#include -#include -#include - -#include -#include -#include - -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // Scene #include @@ -49,17 +44,19 @@ // Event #include +#include +#include // Utils -#include #include #include // Services -#include #include #include #include +#include +#include // Application #include diff --git a/Extra2D/include/extra2d/graphics/camera.h b/Extra2D/include/extra2d/graphics/camera/camera.h similarity index 100% rename from Extra2D/include/extra2d/graphics/camera.h rename to Extra2D/include/extra2d/graphics/camera/camera.h diff --git a/Extra2D/include/extra2d/graphics/viewport_adapter.h b/Extra2D/include/extra2d/graphics/camera/viewport_adapter.h similarity index 100% rename from Extra2D/include/extra2d/graphics/viewport_adapter.h rename to Extra2D/include/extra2d/graphics/camera/viewport_adapter.h diff --git a/Extra2D/include/extra2d/graphics/render_backend.h b/Extra2D/include/extra2d/graphics/core/render_backend.h similarity index 100% rename from Extra2D/include/extra2d/graphics/render_backend.h rename to Extra2D/include/extra2d/graphics/core/render_backend.h diff --git a/Extra2D/include/extra2d/graphics/render_command.h b/Extra2D/include/extra2d/graphics/core/render_command.h similarity index 99% rename from Extra2D/include/extra2d/graphics/render_command.h rename to Extra2D/include/extra2d/graphics/core/render_command.h index 3caeb04..d2c3aa0 100644 --- a/Extra2D/include/extra2d/graphics/render_command.h +++ b/Extra2D/include/extra2d/graphics/core/render_command.h @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/Extra2D/include/extra2d/graphics/core/render_module.h b/Extra2D/include/extra2d/graphics/core/render_module.h new file mode 100644 index 0000000..bb848bf --- /dev/null +++ b/Extra2D/include/extra2d/graphics/core/render_module.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include + +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 deps() const override { + return {std::type_index(typeid(WindowModule))}; + } + + /** + * @brief 获取渲染器 + * @return 渲染后端指针 + */ + RenderBackend* renderer() const { return renderer_.get(); } + +private: + Cfg cfg_; + UniquePtr renderer_; + bool initialized_ = false; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/render_target.h b/Extra2D/include/extra2d/graphics/core/render_target.h similarity index 99% rename from Extra2D/include/extra2d/graphics/render_target.h rename to Extra2D/include/extra2d/graphics/core/render_target.h index 90b0ea2..47433a5 100644 --- a/Extra2D/include/extra2d/graphics/render_target.h +++ b/Extra2D/include/extra2d/graphics/core/render_target.h @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include namespace extra2d { diff --git a/Extra2D/include/extra2d/graphics/gpu_context.h b/Extra2D/include/extra2d/graphics/memory/gpu_context.h similarity index 100% rename from Extra2D/include/extra2d/graphics/gpu_context.h rename to Extra2D/include/extra2d/graphics/memory/gpu_context.h diff --git a/Extra2D/include/extra2d/graphics/vram_manager.h b/Extra2D/include/extra2d/graphics/memory/vram_manager.h similarity index 100% rename from Extra2D/include/extra2d/graphics/vram_manager.h rename to Extra2D/include/extra2d/graphics/memory/vram_manager.h diff --git a/Extra2D/include/extra2d/graphics/opengl/gl_font_atlas.h b/Extra2D/include/extra2d/graphics/opengl/gl_font_atlas.h index a3e8d65..5b524aa 100644 --- a/Extra2D/include/extra2d/graphics/opengl/gl_font_atlas.h +++ b/Extra2D/include/extra2d/graphics/opengl/gl_font_atlas.h @@ -3,9 +3,9 @@ #include #include #include -#include +#include #include -#include +#include #include #include #include diff --git a/Extra2D/include/extra2d/graphics/opengl/gl_renderer.h b/Extra2D/include/extra2d/graphics/opengl/gl_renderer.h index b2bd972..4c369ea 100644 --- a/Extra2D/include/extra2d/graphics/opengl/gl_renderer.h +++ b/Extra2D/include/extra2d/graphics/opengl/gl_renderer.h @@ -1,8 +1,8 @@ #pragma once #include -#include -#include +#include +#include #include #include diff --git a/Extra2D/include/extra2d/graphics/opengl/gl_shader.h b/Extra2D/include/extra2d/graphics/opengl/gl_shader.h index 3b01db1..f61c7b5 100644 --- a/Extra2D/include/extra2d/graphics/opengl/gl_shader.h +++ b/Extra2D/include/extra2d/graphics/opengl/gl_shader.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include #include diff --git a/Extra2D/include/extra2d/graphics/opengl/gl_sprite_batch.h b/Extra2D/include/extra2d/graphics/opengl/gl_sprite_batch.h index 3c43cba..1609a2f 100644 --- a/Extra2D/include/extra2d/graphics/opengl/gl_sprite_batch.h +++ b/Extra2D/include/extra2d/graphics/opengl/gl_sprite_batch.h @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include diff --git a/Extra2D/include/extra2d/graphics/opengl/gl_texture.h b/Extra2D/include/extra2d/graphics/opengl/gl_texture.h index 8d559a7..df9cd6d 100644 --- a/Extra2D/include/extra2d/graphics/opengl/gl_texture.h +++ b/Extra2D/include/extra2d/graphics/opengl/gl_texture.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include #include diff --git a/Extra2D/include/extra2d/graphics/shader/shader_cache.h b/Extra2D/include/extra2d/graphics/shader/shader_cache.h new file mode 100644 index 0000000..1ab93f5 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/shader/shader_cache.h @@ -0,0 +1,131 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { + +// ============================================================================ +// Shader缓存条目 +// ============================================================================ +struct ShaderCacheEntry { + std::string name; + std::string sourceHash; + uint64_t compileTime = 0; + std::vector binary; + std::vector 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 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 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 diff --git a/Extra2D/include/extra2d/graphics/shader/shader_hot_reloader.h b/Extra2D/include/extra2d/graphics/shader/shader_hot_reloader.h new file mode 100644 index 0000000..d5ddebc --- /dev/null +++ b/Extra2D/include/extra2d/graphics/shader/shader_hot_reloader.h @@ -0,0 +1,137 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +namespace extra2d { + +// ============================================================================ +// 文件变化事件 +// ============================================================================ +struct FileChangeEvent { + std::string filepath; + + enum class Type { + Created, + Modified, + Deleted, + Renamed + } type; + + uint64_t timestamp = 0; +}; + +// ============================================================================ +// 文件变化回调 +// ============================================================================ +using FileChangeCallback = std::function; + +// ============================================================================ +// 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& 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 filePaths; + FileChangeCallback callback; + std::unordered_map modifiedTimes; + }; + std::unordered_map watchMap_; + +#ifdef _WIN32 + HANDLE watchHandle_ = nullptr; + std::vector 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 diff --git a/Extra2D/include/extra2d/graphics/shader_interface.h b/Extra2D/include/extra2d/graphics/shader/shader_interface.h similarity index 100% rename from Extra2D/include/extra2d/graphics/shader_interface.h rename to Extra2D/include/extra2d/graphics/shader/shader_interface.h diff --git a/Extra2D/include/extra2d/graphics/shader_loader.h b/Extra2D/include/extra2d/graphics/shader/shader_loader.h similarity index 100% rename from Extra2D/include/extra2d/graphics/shader_loader.h rename to Extra2D/include/extra2d/graphics/shader/shader_loader.h diff --git a/Extra2D/include/extra2d/graphics/shader_manager.h b/Extra2D/include/extra2d/graphics/shader/shader_manager.h similarity index 62% rename from Extra2D/include/extra2d/graphics/shader_manager.h rename to Extra2D/include/extra2d/graphics/shader/shader_manager.h index 677a648..3bbc72e 100644 --- a/Extra2D/include/extra2d/graphics/shader_manager.h +++ b/Extra2D/include/extra2d/graphics/shader/shader_manager.h @@ -1,52 +1,22 @@ #pragma once -#include -#include +#include +#include +#include +#include +#include #include #include -#include - -#ifdef _WIN32 -#include -#endif namespace extra2d { -// ============================================================================ -// Shader缓存条目 -// ============================================================================ -struct ShaderCacheEntry { - std::string name; - std::string sourceHash; - uint64_t compileTime = 0; - std::vector binary; - std::vector dependencies; -}; - -// ============================================================================ -// 文件变化事件 -// ============================================================================ -struct FileChangeEvent { - std::string filepath; - - enum class Type { - Created, - Modified, - Deleted, - Renamed - } type; - - uint64_t timestamp = 0; -}; - // ============================================================================ // Shader重载回调 // ============================================================================ using ShaderReloadCallback = std::function newShader)>; -using FileChangeCallback = std::function; // ============================================================================ -// 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 loadFromFiles(const std::string& name, const std::string& vertPath, const std::string& fragPath); + /** + * @brief 从组合文件加载Shader + * @param path 组合Shader文件路径 + * @return 加载的Shader实例 + */ Ptr loadFromCombinedFile(const std::string& path); + /** + * @brief 从源码加载Shader + * @param name Shader名称 + * @param vertSource 顶点着色器源码 + * @param fragSource 片段着色器源码 + * @return 加载的Shader实例 + */ Ptr loadFromSource(const std::string& name, const std::string& vertSource, const std::string& fragSource); + /** + * @brief 获取已加载的Shader + * @param name Shader名称 + * @return Shader实例,不存在返回nullptr + */ Ptr 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& filePaths, - FileChangeCallback callback); - void unwatch(const std::string& shaderName); - // ------------------------------------------------------------------------ // 内置Shader // ------------------------------------------------------------------------ + /** + * @brief 获取内置Shader + * @param name 内置Shader名称 + * @return Shader实例 + */ Ptr 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 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 factory_; @@ -184,45 +219,29 @@ private: bool hotReloadEnabled_ = false; bool hotReloadSupported_ = true; - // 缓存(原 ShaderCache) - std::unordered_map 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 filePaths; - FileChangeCallback callback; - std::unordered_map modifiedTimes; - }; - std::unordered_map watchMap_; - -#ifdef _WIN32 - HANDLE watchHandle_ = nullptr; - std::vector 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 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); }; diff --git a/Extra2D/include/extra2d/graphics/shader/shader_preset.h b/Extra2D/include/extra2d/graphics/shader/shader_preset.h new file mode 100644 index 0000000..5014861 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/shader/shader_preset.h @@ -0,0 +1,112 @@ +#pragma once + +#include +#include +#include +#include + +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 Water(const WaterParams& params = {}); + + /** + * @brief 创建描边效果着色器 + * @param params 描边效果参数 + * @return 配置好的着色器 + */ + static Ptr Outline(const OutlineParams& params = {}); + + /** + * @brief 创建扭曲效果着色器 + * @param params 扭曲效果参数 + * @return 配置好的着色器 + */ + static Ptr Distortion(const DistortionParams& params = {}); + + /** + * @brief 创建像素化效果着色器 + * @param params 像素化效果参数 + * @return 配置好的着色器 + */ + static Ptr Pixelate(const PixelateParams& params = {}); + + /** + * @brief 创建反相效果着色器 + * @param params 反相效果参数 + * @return 配置好的着色器 + */ + static Ptr Invert(const InvertParams& params = {}); + + /** + * @brief 创建灰度效果着色器 + * @param params 灰度效果参数 + * @return 配置好的着色器 + */ + static Ptr Grayscale(const GrayscaleParams& params = {}); + + /** + * @brief 创建模糊效果着色器 + * @param params 模糊效果参数 + * @return 配置好的着色器 + */ + static Ptr Blur(const BlurParams& params = {}); + + /** + * @brief 创建灰度+描边组合效果着色器 + * @param grayParams 灰度效果参数 + * @param outlineParams 描边效果参数 + * @return 配置好的着色器 + */ + static Ptr GrayscaleOutline(const GrayscaleParams& grayParams, + const OutlineParams& outlineParams); + + /** + * @brief 创建像素化+反相组合效果着色器 + * @param pixParams 像素化效果参数 + * @param invParams 反相效果参数 + * @return 配置好的着色器 + */ + static Ptr PixelateInvert(const PixelateParams& pixParams, + const InvertParams& invParams); +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/alpha_mask.h b/Extra2D/include/extra2d/graphics/texture/alpha_mask.h similarity index 100% rename from Extra2D/include/extra2d/graphics/alpha_mask.h rename to Extra2D/include/extra2d/graphics/texture/alpha_mask.h diff --git a/Extra2D/include/extra2d/graphics/font.h b/Extra2D/include/extra2d/graphics/texture/font.h similarity index 100% rename from Extra2D/include/extra2d/graphics/font.h rename to Extra2D/include/extra2d/graphics/texture/font.h diff --git a/Extra2D/include/extra2d/graphics/texture.h b/Extra2D/include/extra2d/graphics/texture/texture.h similarity index 100% rename from Extra2D/include/extra2d/graphics/texture.h rename to Extra2D/include/extra2d/graphics/texture/texture.h diff --git a/Extra2D/include/extra2d/graphics/texture_atlas.h b/Extra2D/include/extra2d/graphics/texture/texture_atlas.h similarity index 99% rename from Extra2D/include/extra2d/graphics/texture_atlas.h rename to Extra2D/include/extra2d/graphics/texture/texture_atlas.h index 68b891b..975ef58 100644 --- a/Extra2D/include/extra2d/graphics/texture_atlas.h +++ b/Extra2D/include/extra2d/graphics/texture/texture_atlas.h @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/Extra2D/include/extra2d/graphics/texture_pool.h b/Extra2D/include/extra2d/graphics/texture/texture_pool.h similarity index 99% rename from Extra2D/include/extra2d/graphics/texture_pool.h rename to Extra2D/include/extra2d/graphics/texture/texture_pool.h index 2897fa9..8a83049 100644 --- a/Extra2D/include/extra2d/graphics/texture_pool.h +++ b/Extra2D/include/extra2d/graphics/texture/texture_pool.h @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include diff --git a/Extra2D/include/extra2d/input/input_config.h b/Extra2D/include/extra2d/input/input_config.h deleted file mode 100644 index 98924a6..0000000 --- a/Extra2D/include/extra2d/input/input_config.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include - -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; } -}; - -} diff --git a/Extra2D/include/extra2d/scene/node.h b/Extra2D/include/extra2d/scene/node.h index 10433c6..7ed2397 100644 --- a/Extra2D/include/extra2d/scene/node.h +++ b/Extra2D/include/extra2d/scene/node.h @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/Extra2D/include/extra2d/scene/scene.h b/Extra2D/include/extra2d/scene/scene.h index 04c3a70..aad87cf 100644 --- a/Extra2D/include/extra2d/scene/scene.h +++ b/Extra2D/include/extra2d/scene/scene.h @@ -1,8 +1,7 @@ #pragma once #include -#include -#include +#include #include #include @@ -14,7 +13,7 @@ struct RenderCommand; // ============================================================================ // 场景类 - 节点容器,管理整个场景图 // ============================================================================ -class E2D_API Scene : public Node { +class Scene : public Node { public: Scene(); ~Scene() override = default; diff --git a/Extra2D/include/extra2d/scene/sprite.h b/Extra2D/include/extra2d/scene/sprite.h index 185ac29..37c48ea 100644 --- a/Extra2D/include/extra2d/scene/sprite.h +++ b/Extra2D/include/extra2d/scene/sprite.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include namespace extra2d { diff --git a/Extra2D/include/extra2d/scene/transition_box_scene.h b/Extra2D/include/extra2d/scene/transition_box_scene.h index 0a8a298..de9ed25 100644 --- a/Extra2D/include/extra2d/scene/transition_box_scene.h +++ b/Extra2D/include/extra2d/scene/transition_box_scene.h @@ -26,6 +26,7 @@ public: protected: void onTransitionStart() override; void renderContent(RenderBackend &renderer) override; + void updateTransition(float dt); private: int divisions_; diff --git a/Extra2D/include/extra2d/scene/transition_fade_scene.h b/Extra2D/include/extra2d/scene/transition_fade_scene.h index 41cd7c1..541bf26 100644 --- a/Extra2D/include/extra2d/scene/transition_fade_scene.h +++ b/Extra2D/include/extra2d/scene/transition_fade_scene.h @@ -34,6 +34,12 @@ protected: */ void onTransitionStart() override; + /** + * @brief 更新过渡进度 + * @param dt 帧间隔时间(秒) + */ + void updateTransition(float dt) override; + /** * @brief 渲染内容 * 根据进度控制新旧场景的显示 diff --git a/Extra2D/include/extra2d/scene/transition_flip_scene.h b/Extra2D/include/extra2d/scene/transition_flip_scene.h index 9a0122b..fa470ff 100644 --- a/Extra2D/include/extra2d/scene/transition_flip_scene.h +++ b/Extra2D/include/extra2d/scene/transition_flip_scene.h @@ -29,6 +29,7 @@ public: protected: void onTransitionStart() override; void renderContent(RenderBackend &renderer) override; + void updateTransition(float dt); private: Axis axis_; diff --git a/Extra2D/include/extra2d/scene/transition_scale_scene.h b/Extra2D/include/extra2d/scene/transition_scale_scene.h index 0f1972e..cad1948 100644 --- a/Extra2D/include/extra2d/scene/transition_scale_scene.h +++ b/Extra2D/include/extra2d/scene/transition_scale_scene.h @@ -24,6 +24,7 @@ public: protected: void onTransitionStart() override; void renderContent(RenderBackend &renderer) override; + void updateTransition(float dt); }; } // namespace extra2d diff --git a/Extra2D/include/extra2d/scene/transition_scene.h b/Extra2D/include/extra2d/scene/transition_scene.h index 93c9b2f..1e15e47 100644 --- a/Extra2D/include/extra2d/scene/transition_scene.h +++ b/Extra2D/include/extra2d/scene/transition_scene.h @@ -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 inScene_; // 要进入的场景 Ptr outScene_; // 要退出的场景 FinishCallback finishCallback_; + FinishCallback cancelCallback_; // 取消回调 + + friend class SceneManager; }; } // namespace extra2d diff --git a/Extra2D/include/extra2d/scene/transition_slide_scene.h b/Extra2D/include/extra2d/scene/transition_slide_scene.h index f41e490..7775182 100644 --- a/Extra2D/include/extra2d/scene/transition_slide_scene.h +++ b/Extra2D/include/extra2d/scene/transition_slide_scene.h @@ -27,6 +27,7 @@ public: protected: void onTransitionStart() override; void renderContent(RenderBackend &renderer) override; + void updateTransition(float dt); private: TransitionDirection direction_; diff --git a/Extra2D/include/extra2d/services/camera_service.h b/Extra2D/include/extra2d/services/camera_service.h index 5143377..948a187 100644 --- a/Extra2D/include/extra2d/services/camera_service.h +++ b/Extra2D/include/extra2d/services/camera_service.h @@ -1,8 +1,8 @@ #pragma once #include -#include -#include +#include +#include namespace extra2d { diff --git a/Extra2D/include/extra2d/services/logger_service.h b/Extra2D/include/extra2d/services/logger_service.h new file mode 100644 index 0000000..12a1245 --- /dev/null +++ b/Extra2D/include/extra2d/services/logger_service.h @@ -0,0 +1,228 @@ +#pragma once + +#include +#include +#include +#include +#include + +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_; +}; + +} // namespace extra2d + +// 格式化辅助函数 - 将参数转换为字符串 +namespace extra2d { +namespace detail { + template + std::string to_string(T&& value) { + using Decayed = std::decay_t; + if constexpr (std::is_same_v) { + return value; + } else if constexpr (std::is_same_v) { + return value ? value : "(null)"; + } else if constexpr (std::is_arithmetic_v) { + if constexpr (std::is_same_v) { + return value ? "true" : "false"; + } else if constexpr (std::is_floating_point_v) { + 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 + 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(value)); + format_impl(result, p + 2, std::forward(args)...); + return; + } + result += *p++; + } + // 没有更多的 {},追加剩余参数(不应该发生) + result += " "; + result += to_string(std::forward(value)); + format_impl(result, p, std::forward(args)...); + } +} + +template +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)...); + 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 diff --git a/Extra2D/include/extra2d/utils/logger.h b/Extra2D/include/extra2d/utils/logger.h index cff4a7e..086f3f9 100644 --- a/Extra2D/include/extra2d/utils/logger.h +++ b/Extra2D/include/extra2d/utils/logger.h @@ -1,202 +1,12 @@ #pragma once -#include -#include -#include -#include -#include -#include +/** + * @file logger.h + * @brief 日志工具头文件 + * + * 提供便捷的日志宏定义,实际实现位于 logger_service.h + */ -namespace extra2d { +#include +#include -enum class LogLevel { - Trace = 0, - Debug = 1, - Info = 2, - Warn = 3, - Error = 4, - Fatal = 5, - Off = 6 -}; - -namespace detail { - -template inline std::string to_string_arg(const T &value) { - if constexpr (std::is_same_v) { - return value; - } else if constexpr (std::is_same_v || - std::is_same_v) { - return value ? std::string(value) : std::string("(null)"); - } else if constexpr (std::is_same_v) { - return value ? "true" : "false"; - } else if constexpr (std::is_arithmetic_v) { - if constexpr (std::is_floating_point_v) { - char buf[64]; - snprintf(buf, sizeof(buf), "%.2f", static_cast(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 -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) { - char buf[32]; - snprintf(buf, sizeof(buf), "0x%x", - static_cast(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) { - 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(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 -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 - static void log(LogLevel level, const char *fmt, const Args &...args) { - if (static_cast(level) < static_cast(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(level) < static_cast(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 diff --git a/Extra2D/src/app/application.cpp b/Extra2D/src/app/application.cpp index 59cf932..b2a5bb8 100644 --- a/Extra2D/src/app/application.cpp +++ b/Extra2D/src/app/application.cpp @@ -1,366 +1,268 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include +#include +#include +#include +#include #include +#include namespace extra2d { static double getTimeSeconds() { #ifdef __SWITCH__ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return static_cast(ts.tv_sec) + - static_cast(ts.tv_nsec) / 1000000000.0; + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return static_cast(ts.tv_sec) + + static_cast(ts.tv_nsec) / 1000000000.0; #else - using namespace std::chrono; - auto now = steady_clock::now(); - auto duration = now.time_since_epoch(); - return duration_cast>(duration).count(); + using namespace std::chrono; + auto now = steady_clock::now(); + auto duration = now.time_since_epoch(); + return duration_cast>(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(); - 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(); - if (inputModule) { - inputModule->setWindow(window_); - } - - auto renderModule = getModule(); - if (renderModule) { - renderModule->setWindow(window_); - } - - // 注册 CameraService(需要 window) - registerCameraService(); - - if (!ServiceLocator::instance().initializeAll()) { - return false; - } - - auto cameraService = ServiceLocator::instance().getService(); - if (cameraService && window_) { - window_->onResize([cameraService](int width, int height) { - cameraService->updateViewport(width, height); - cameraService->applyViewportAdapter(); - - auto sceneService = - ServiceLocator::instance().getService(); - if (sceneService) { - auto currentScene = sceneService->getCurrentScene(); - if (currentScene) { - currentScene->setViewportSize(static_cast(width), - static_cast(height)); - } - } - }); - } - - initialized_ = true; - running_ = true; - - return true; -} - -bool Application::init(const std::string &configPath) { - if (initialized_) { - return true; - } - - auto configModule = getModule(); - if (configModule) { - configModule->setConfigPath(configPath); - } - - AppConfig cfg; - return init(cfg); -} - -bool Application::initModules(const AppConfig &config) { - auto configModule = getModule(); - 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()) { - locator.registerService(makeShared()); - } - - if (!locator.hasService()) { - locator.registerService(makeShared()); - } - - if (!locator.hasService()) { - locator.registerService(makeShared()); - } -} - -void Application::registerCameraService() { - auto &locator = ServiceLocator::instance(); - - if (!locator.hasService()) { - auto cameraService = makeShared(); - if (window_) { - cameraService->setViewport(0, static_cast(window_->width()), - static_cast(window_->height()), 0); - ViewportConfig vpConfig; - vpConfig.logicWidth = static_cast(window_->width()); - vpConfig.logicHeight = static_cast(window_->height()); - vpConfig.mode = ViewportMode::AspectRatio; - cameraService->setViewportConfig(vpConfig); - cameraService->updateViewport(window_->width(), window_->height()); - } - locator.registerService(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()) { + auto logger = makeShared(); + locator.registerService(std::static_pointer_cast(logger)); + } + + // 初始化所有模块(拓扑排序) + if (!Registry::instance().init()) { + return false; + } + + // 模块初始化完成后,注册其他核心服务 + registerCoreServices(); + + initialized_ = true; + running_ = true; + return true; +} + +void Application::registerCoreServices() { + auto& locator = ServiceLocator::instance(); + + if (!locator.hasService()) { + auto service = makeShared(); + locator.registerService(std::static_pointer_cast(service)); + } + + if (!locator.hasService()) { + auto service = makeShared(); + locator.registerService(std::static_pointer_cast(service)); + } + + if (!locator.hasService()) { + auto service = makeShared(); + locator.registerService(std::static_pointer_cast(service)); + } + + auto* winMod = get(); + if (winMod && winMod->win() && !locator.hasService()) { + auto cameraService = makeShared(); + auto* win = winMod->win(); + cameraService->setViewport(0, static_cast(win->width()), + static_cast(win->height()), 0); + ViewportConfig vpConfig; + vpConfig.logicWidth = static_cast(win->width()); + vpConfig.logicHeight = static_cast(win->height()); + vpConfig.mode = ViewportMode::AspectRatio; + cameraService->setViewportConfig(vpConfig); + cameraService->updateViewport(win->width(), win->height()); + locator.registerService(std::static_pointer_cast(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(); + 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(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(); - if (eventService) { - eventService->processQueue(); - } - - if (!paused_) { - update(); - } - - render(); - - ConfigManager::instance().update(deltaTime_); + double currentTime = getTimeSeconds(); + deltaTime_ = static_cast(currentTime - lastFrameTime_); + lastFrameTime_ = currentTime; + + totalTime_ += deltaTime_; + + frameCount_++; + fpsTimer_ += deltaTime_; + if (fpsTimer_ >= 1.0f) { + currentFps_ = frameCount_; + frameCount_ = 0; + fpsTimer_ -= 1.0f; + } + + auto* winMod = get(); + if (winMod && winMod->win()) { + winMod->win()->poll(); + } + + auto eventService = ServiceLocator::instance().getService(); + if (eventService) { + eventService->processQueue(); + } + + if (!paused_) { + update(); + } + + render(); + + // 帧率限制 + auto* renderMod = get(); + 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(); + if (inputMod) { + inputMod->update(); + } } void Application::render() { - RenderBackend *renderer = nullptr; - auto renderModule = getModule(); - if (renderModule) { - renderer = renderModule->getRenderer(); - } - - if (!renderer) { - return; - } - - auto cameraService = ServiceLocator::instance().getService(); - if (cameraService) { - const auto &vp = cameraService->getViewportResult().viewport; - renderer->setViewport( - static_cast(vp.origin.x), static_cast(vp.origin.y), - static_cast(vp.size.width), static_cast(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(); + if (!renderMod || !renderMod->renderer()) return; + + auto* renderer = renderMod->renderer(); + auto* winMod = get(); + if (!winMod || !winMod->win()) return; + + auto cameraService = ServiceLocator::instance().getService(); + if (cameraService) { + const auto& vp = cameraService->getViewportResult().viewport; + renderer->setViewport( + static_cast(vp.origin.x), static_cast(vp.origin.y), + static_cast(vp.size.width), static_cast(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(); + if (sceneService) { + sceneService->render(*renderer); } - } - - auto sceneService = ServiceLocator::instance().getService(); - 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(); - 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(); + return winMod ? winMod->win() : nullptr; } -SharedPtr Application::scenes() { - return ServiceLocator::instance().getService(); +RenderBackend* Application::renderer() { + auto* renderMod = get(); + return renderMod ? renderMod->renderer() : nullptr; } -SharedPtr Application::timers() { - return ServiceLocator::instance().getService(); -} - -SharedPtr Application::events() { - return ServiceLocator::instance().getService(); -} - -SharedPtr Application::camera() { - return ServiceLocator::instance().getService(); +IInput* Application::input() { + auto* winMod = get(); + return (winMod && winMod->win()) ? winMod->win()->input() : nullptr; } void Application::enterScene(Ptr scene) { - auto sceneService = ServiceLocator::instance().getService(); - if (sceneService && scene) { - scene->setViewportSize(static_cast(window_->width()), - static_cast(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(); + auto* winMod = get(); + if (sceneService && scene && winMod && winMod->win()) { + scene->setViewportSize(static_cast(winMod->win()->width()), + static_cast(winMod->win()->height())); + sceneService->enterScene(scene); + } } } // namespace extra2d diff --git a/Extra2D/src/config/config_loader_ini.cpp b/Extra2D/src/config/config_loader_ini.cpp deleted file mode 100644 index ae0942b..0000000 --- a/Extra2D/src/config/config_loader_ini.cpp +++ /dev/null @@ -1,289 +0,0 @@ -#include -#include -#include - -#include -#include -#include -#include - -namespace extra2d { - -static std::string trim(const std::string &str) { - size_t start = 0; - while (start < str.length() && - std::isspace(static_cast(str[start]))) { - ++start; - } - size_t end = str.length(); - while (end > start && - std::isspace(static_cast(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(std::tolower(static_cast(c))); - } - return result; -} - -using IniData = std::map>; - -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(); - } - } 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(std::tolower(c)); - return ext == ".ini"; - } - return false; -} - -UniquePtr IniConfigLoader::clone() const { - return makeUnique(); -} - -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); -} - -} diff --git a/Extra2D/src/config/config_loader_json.cpp b/Extra2D/src/config/config_loader_json.cpp deleted file mode 100644 index cceaf28..0000000 --- a/Extra2D/src/config/config_loader_json.cpp +++ /dev/null @@ -1,147 +0,0 @@ -#include -#include - -#include -#include -#include - -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(e.byte)); - } - - if (root.contains("appName") && root["appName"].is_string()) { - config.appName = root["appName"].get(); - } - - if (root.contains("appVersion") && root["appVersion"].is_string()) { - config.appVersion = root["appVersion"].get(); - } - - if (root.contains("organization") && root["organization"].is_string()) { - config.organization = root["organization"].get(); - } - - if (root.contains("configFile") && root["configFile"].is_string()) { - config.configFile = root["configFile"].get(); - } - - 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(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(std::tolower(c)); - return ext == ".json"; - } - return false; -} - -UniquePtr JsonConfigLoader::clone() const { - return makeUnique(); -} - -} diff --git a/Extra2D/src/config/config_manager.cpp b/Extra2D/src/config/config_manager.cpp deleted file mode 100644 index 3d2c072..0000000 --- a/Extra2D/src/config/config_manager.cpp +++ /dev/null @@ -1,366 +0,0 @@ -#include -#include -#include - -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 lock(m_mutex); - - m_configPath = configPath; - - m_loader = makeUnique(); - 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 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 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 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 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 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 lock(m_mutex); - - m_appConfig = config; - m_modified = true; - - E2D_LOG_INFO("App config updated"); -} - -int ConfigManager::registerChangeCallback(ConfigChangeCallback callback) { - std::lock_guard 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 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 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 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 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 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 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); - } - } -} - -} diff --git a/Extra2D/src/core/registry.cpp b/Extra2D/src/core/registry.cpp new file mode 100644 index 0000000..f9eb1cb --- /dev/null +++ b/Extra2D/src/core/registry.cpp @@ -0,0 +1,96 @@ +#include +#include +#include +#include +#include +#include + +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 Registry::topologicalSort() { + std::vector result; + std::unordered_map inDegree; + std::unordered_map> 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, 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 diff --git a/Extra2D/src/graphics/camera.cpp b/Extra2D/src/graphics/camera/camera.cpp similarity index 98% rename from Extra2D/src/graphics/camera.cpp rename to Extra2D/src/graphics/camera/camera.cpp index 81a0383..23e074c 100644 --- a/Extra2D/src/graphics/camera.cpp +++ b/Extra2D/src/graphics/camera/camera.cpp @@ -1,6 +1,6 @@ #include -#include -#include +#include +#include #include #include diff --git a/Extra2D/src/graphics/viewport_adapter.cpp b/Extra2D/src/graphics/camera/viewport_adapter.cpp similarity index 99% rename from Extra2D/src/graphics/viewport_adapter.cpp rename to Extra2D/src/graphics/camera/viewport_adapter.cpp index f2c4c01..fe6752a 100644 --- a/Extra2D/src/graphics/viewport_adapter.cpp +++ b/Extra2D/src/graphics/camera/viewport_adapter.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/Extra2D/src/graphics/render_backend.cpp b/Extra2D/src/graphics/core/render_backend.cpp similarity index 91% rename from Extra2D/src/graphics/render_backend.cpp rename to Extra2D/src/graphics/core/render_backend.cpp index d2dae52..7d4b53a 100644 --- a/Extra2D/src/graphics/render_backend.cpp +++ b/Extra2D/src/graphics/core/render_backend.cpp @@ -1,5 +1,5 @@ #include -#include +#include namespace extra2d { diff --git a/Extra2D/src/graphics/render_command.cpp b/Extra2D/src/graphics/core/render_command.cpp similarity index 99% rename from Extra2D/src/graphics/render_command.cpp rename to Extra2D/src/graphics/core/render_command.cpp index a3b6cef..dbfc6ba 100644 --- a/Extra2D/src/graphics/render_command.cpp +++ b/Extra2D/src/graphics/core/render_command.cpp @@ -1,4 +1,4 @@ -#include +#include #include namespace extra2d { diff --git a/Extra2D/src/graphics/core/render_module.cpp b/Extra2D/src/graphics/core/render_module.cpp new file mode 100644 index 0000000..220b2b9 --- /dev/null +++ b/Extra2D/src/graphics/core/render_module.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(); + if (!winMod || !winMod->win()) { + return false; + } + + // 初始化ShaderManager + if (!ShaderManager::getInstance().isInitialized()) { + auto factory = makeShared(); + 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 diff --git a/Extra2D/src/graphics/render_target.cpp b/Extra2D/src/graphics/core/render_target.cpp similarity index 99% rename from Extra2D/src/graphics/render_target.cpp rename to Extra2D/src/graphics/core/render_target.cpp index f1f442a..931084d 100644 --- a/Extra2D/src/graphics/render_target.cpp +++ b/Extra2D/src/graphics/core/render_target.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #define STB_IMAGE_WRITE_IMPLEMENTATION diff --git a/Extra2D/src/graphics/gpu_context.cpp b/Extra2D/src/graphics/memory/gpu_context.cpp similarity index 95% rename from Extra2D/src/graphics/gpu_context.cpp rename to Extra2D/src/graphics/memory/gpu_context.cpp index 7d0a934..93684b7 100644 --- a/Extra2D/src/graphics/gpu_context.cpp +++ b/Extra2D/src/graphics/memory/gpu_context.cpp @@ -1,4 +1,4 @@ -#include +#include namespace extra2d { diff --git a/Extra2D/src/graphics/vram_manager.cpp b/Extra2D/src/graphics/memory/vram_manager.cpp similarity index 98% rename from Extra2D/src/graphics/vram_manager.cpp rename to Extra2D/src/graphics/memory/vram_manager.cpp index 93994c0..b8e11e4 100644 --- a/Extra2D/src/graphics/vram_manager.cpp +++ b/Extra2D/src/graphics/memory/vram_manager.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include namespace extra2d { diff --git a/Extra2D/src/graphics/opengl/gl_renderer.cpp b/Extra2D/src/graphics/opengl/gl_renderer.cpp index efdb694..2e4a6d9 100644 --- a/Extra2D/src/graphics/opengl/gl_renderer.cpp +++ b/Extra2D/src/graphics/opengl/gl_renderer.cpp @@ -2,12 +2,12 @@ #include #include #include -#include +#include #include #include #include -#include -#include +#include +#include #include #include #include diff --git a/Extra2D/src/graphics/opengl/gl_texture.cpp b/Extra2D/src/graphics/opengl/gl_texture.cpp index 30e2682..e68b829 100644 --- a/Extra2D/src/graphics/opengl/gl_texture.cpp +++ b/Extra2D/src/graphics/opengl/gl_texture.cpp @@ -1,6 +1,6 @@ #include -#include -#include +#include +#include #define STB_IMAGE_IMPLEMENTATION #include #include diff --git a/Extra2D/src/graphics/shader/shader_cache.cpp b/Extra2D/src/graphics/shader/shader_cache.cpp new file mode 100644 index 0000000..ca2a044 --- /dev/null +++ b/Extra2D/src/graphics/shader/shader_cache.cpp @@ -0,0 +1,286 @@ +#include +#include +#include +#include +#include +#include + +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 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(it->second); + entry->binary.clear(); + + file.seekg(0, std::ios::end); + size_t fileSize = static_cast(file.tellg()); + file.seekg(0, std::ios::beg); + + entry->binary.resize(fileSize); + file.read(reinterpret_cast(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(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(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( + 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 diff --git a/Extra2D/src/graphics/shader/shader_hot_reloader.cpp b/Extra2D/src/graphics/shader/shader_hot_reloader.cpp new file mode 100644 index 0000000..1afd919 --- /dev/null +++ b/Extra2D/src/graphics/shader/shader_hot_reloader.cpp @@ -0,0 +1,164 @@ +#include +#include +#include +#include + +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& 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( + 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( + ftime - fs::file_time_type::clock::now() + std::chrono::system_clock::now()); + return static_cast(sctp.time_since_epoch().count()); + } catch (...) { + return 0; + } +} + +} // namespace extra2d diff --git a/Extra2D/src/graphics/shader_loader.cpp b/Extra2D/src/graphics/shader/shader_loader.cpp similarity index 99% rename from Extra2D/src/graphics/shader_loader.cpp rename to Extra2D/src/graphics/shader/shader_loader.cpp index 6444b59..43c3599 100644 --- a/Extra2D/src/graphics/shader_loader.cpp +++ b/Extra2D/src/graphics/shader/shader_loader.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include diff --git a/Extra2D/src/graphics/shader_manager.cpp b/Extra2D/src/graphics/shader/shader_manager.cpp similarity index 99% rename from Extra2D/src/graphics/shader_manager.cpp rename to Extra2D/src/graphics/shader/shader_manager.cpp index 5130331..147cd0f 100644 --- a/Extra2D/src/graphics/shader_manager.cpp +++ b/Extra2D/src/graphics/shader/shader_manager.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include diff --git a/Extra2D/src/graphics/shader/shader_preset.cpp b/Extra2D/src/graphics/shader/shader_preset.cpp new file mode 100644 index 0000000..8f3d227 --- /dev/null +++ b/Extra2D/src/graphics/shader/shader_preset.cpp @@ -0,0 +1,184 @@ +#include +#include +#include + +namespace extra2d { + +/** + * @brief 创建水波纹效果着色器 + * @param params 水波纹效果参数 + * @return 配置好的着色器 + */ +Ptr ShaderPreset::Water(const WaterParams ¶ms) { + Ptr 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 ShaderPreset::Outline(const OutlineParams ¶ms) { + Ptr 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 ShaderPreset::Distortion(const DistortionParams ¶ms) { + Ptr 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 ShaderPreset::Pixelate(const PixelateParams ¶ms) { + Ptr 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 ShaderPreset::Invert(const InvertParams ¶ms) { + Ptr 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 ShaderPreset::Grayscale(const GrayscaleParams ¶ms) { + Ptr 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 ShaderPreset::Blur(const BlurParams ¶ms) { + Ptr 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 +ShaderPreset::GrayscaleOutline(const GrayscaleParams &grayParams, + const OutlineParams &outlineParams) { + std::string shaderDir = ShaderManager::getInstance().getShaderDir(); + std::string shaderPath = shaderDir + "effects/grayscale_outline.shader"; + + Ptr 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 ShaderPreset::PixelateInvert(const PixelateParams &pixParams, + const InvertParams &invParams) { + std::string shaderDir = ShaderManager::getInstance().getShaderDir(); + std::string shaderPath = shaderDir + "effects/pixelate_invert.shader"; + + Ptr 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 diff --git a/Extra2D/src/graphics/alpha_mask.cpp b/Extra2D/src/graphics/texture/alpha_mask.cpp similarity index 98% rename from Extra2D/src/graphics/alpha_mask.cpp rename to Extra2D/src/graphics/texture/alpha_mask.cpp index 2791653..bedb0c8 100644 --- a/Extra2D/src/graphics/alpha_mask.cpp +++ b/Extra2D/src/graphics/texture/alpha_mask.cpp @@ -1,4 +1,4 @@ -#include +#include namespace extra2d { diff --git a/Extra2D/src/graphics/texture_atlas.cpp b/Extra2D/src/graphics/texture/texture_atlas.cpp similarity index 99% rename from Extra2D/src/graphics/texture_atlas.cpp rename to Extra2D/src/graphics/texture/texture_atlas.cpp index 0c47469..5348c05 100644 --- a/Extra2D/src/graphics/texture_atlas.cpp +++ b/Extra2D/src/graphics/texture/texture_atlas.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include diff --git a/Extra2D/src/graphics/texture_pool.cpp b/Extra2D/src/graphics/texture/texture_pool.cpp similarity index 99% rename from Extra2D/src/graphics/texture_pool.cpp rename to Extra2D/src/graphics/texture/texture_pool.cpp index ea5b6c5..4e71e45 100644 --- a/Extra2D/src/graphics/texture_pool.cpp +++ b/Extra2D/src/graphics/texture/texture_pool.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include #include diff --git a/Extra2D/src/scene/node.cpp b/Extra2D/src/scene/node.cpp index ff5d033..b50a7c6 100644 --- a/Extra2D/src/scene/node.cpp +++ b/Extra2D/src/scene/node.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include diff --git a/Extra2D/src/scene/scene.cpp b/Extra2D/src/scene/scene.cpp index 05670c4..126eb6e 100644 --- a/Extra2D/src/scene/scene.cpp +++ b/Extra2D/src/scene/scene.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include #include diff --git a/Extra2D/src/scene/scene_manager.cpp b/Extra2D/src/scene/scene_manager.cpp index 8fb2e94..8b29644 100644 --- a/Extra2D/src/scene/scene_manager.cpp +++ b/Extra2D/src/scene/scene_manager.cpp @@ -1,7 +1,7 @@ #include #include -#include -#include +#include +#include #include #include #include @@ -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 = diff --git a/Extra2D/src/scene/shape_node.cpp b/Extra2D/src/scene/shape_node.cpp index 95c8a88..676bc78 100644 --- a/Extra2D/src/scene/shape_node.cpp +++ b/Extra2D/src/scene/shape_node.cpp @@ -1,7 +1,7 @@ #include #include -#include -#include +#include +#include #include #include diff --git a/Extra2D/src/scene/sprite.cpp b/Extra2D/src/scene/sprite.cpp index d5368c4..eae5658 100644 --- a/Extra2D/src/scene/sprite.cpp +++ b/Extra2D/src/scene/sprite.cpp @@ -1,8 +1,8 @@ #include #include -#include -#include -#include +#include +#include +#include #include namespace extra2d { diff --git a/Extra2D/src/scene/transition_box_scene.cpp b/Extra2D/src/scene/transition_box_scene.cpp index 00ca3c3..e4e0fe0 100644 --- a/Extra2D/src/scene/transition_box_scene.cpp +++ b/Extra2D/src/scene/transition_box_scene.cpp @@ -1,7 +1,8 @@ #include #include #include -#include +#include +#include #include #include @@ -38,6 +39,14 @@ Ptr 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(app.window().width()); - float windowHeight = static_cast(app.window().height()); - - elapsed_ += 1.0f / 60.0f; - progress_ = duration_ > 0.0f ? std::min(1.0f, elapsed_ / duration_) : 1.0f; + float windowWidth = static_cast(app.window()->width()); + float windowHeight = static_cast(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 diff --git a/Extra2D/src/scene/transition_fade_scene.cpp b/Extra2D/src/scene/transition_fade_scene.cpp index 31a9b7c..9767dbb 100644 --- a/Extra2D/src/scene/transition_fade_scene.cpp +++ b/Extra2D/src/scene/transition_fade_scene.cpp @@ -1,6 +1,7 @@ #include #include -#include +#include +#include #include #include #include @@ -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(app.window().width()); - float windowHeight = static_cast(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(app.window()->width()); + float windowHeight = static_cast(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(); - } } /** diff --git a/Extra2D/src/scene/transition_flip_scene.cpp b/Extra2D/src/scene/transition_flip_scene.cpp index 4d5c077..7b0119a 100644 --- a/Extra2D/src/scene/transition_flip_scene.cpp +++ b/Extra2D/src/scene/transition_flip_scene.cpp @@ -1,7 +1,7 @@ #include #include -#include -#include +#include +#include namespace extra2d { @@ -36,6 +36,14 @@ Ptr 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 diff --git a/Extra2D/src/scene/transition_scale_scene.cpp b/Extra2D/src/scene/transition_scale_scene.cpp index 663345f..7ca6de6 100644 --- a/Extra2D/src/scene/transition_scale_scene.cpp +++ b/Extra2D/src/scene/transition_scale_scene.cpp @@ -1,6 +1,6 @@ #include -#include -#include +#include +#include #include namespace extra2d { @@ -32,6 +32,14 @@ Ptr 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 diff --git a/Extra2D/src/scene/transition_scene.cpp b/Extra2D/src/scene/transition_scene.cpp index f512e9e..c6975ea 100644 --- a/Extra2D/src/scene/transition_scene.cpp +++ b/Extra2D/src/scene/transition_scene.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include 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 渲染后端引用 diff --git a/Extra2D/src/scene/transition_slide_scene.cpp b/Extra2D/src/scene/transition_slide_scene.cpp index 9ab1a85..85d7f0b 100644 --- a/Extra2D/src/scene/transition_slide_scene.cpp +++ b/Extra2D/src/scene/transition_slide_scene.cpp @@ -1,6 +1,6 @@ #include -#include -#include +#include +#include #include namespace extra2d { @@ -35,6 +35,14 @@ Ptr 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 diff --git a/Extra2D/src/services/logger_service.cpp b/Extra2D/src/services/logger_service.cpp new file mode 100644 index 0000000..8305dd7 --- /dev/null +++ b/Extra2D/src/services/logger_service.cpp @@ -0,0 +1,171 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +// ConsoleLogger 实现 +class ConsoleLogger::Impl { +public: + std::mutex mutex_; +}; + +ConsoleLogger::ConsoleLogger() : level_(LogLevel::Info), impl_(std::make_unique()) { + 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(level) >= static_cast(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 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( + 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 diff --git a/Extra2D/src/utils/logger.cpp b/Extra2D/src/utils/logger.cpp deleted file mode 100644 index f1ebdf9..0000000 --- a/Extra2D/src/utils/logger.cpp +++ /dev/null @@ -1,177 +0,0 @@ -#include -#include -#include - -#ifdef _WIN32 -#include -#endif - -#ifdef __SWITCH__ -#include -#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(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(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(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 diff --git a/README.md b/README.md index c993b25..735bc41 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ 高性能、模块化、支持 Nintendo Switch

-[构建指南](#构建指南) | [快速开始](./docs/quick_start.md) | [示例程序](#示例程序) | [模块系统](./docs/module_system.md) +[构建指南](#构建指南) | [快速开始](#快速开始) | [示例程序](#示例程序) | [模块系统](./docs/module_system.md) @@ -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
模块基类] + MR[ModuleRegistry
模块注册表] SL[ServiceLocator
服务定位器] end subgraph Modules["Modules (模块系统)"] direction TB subgraph CoreModules["Core Modules"] - LOGGER[Logger Module
日志系统] CONFIG[Config Module
配置管理] PLATFORM[Platform Module
平台检测] WINDOW[Window Module
窗口管理] end - subgraph FeatureModules["Feature Modules"] + subgraph OtherModules["Feature Modules"] INPUT[Input Module
输入处理] RENDER[Render Module
渲染系统] end @@ -106,9 +106,9 @@ flowchart TB INPUT_SYS[Input System
输入系统] 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 - 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 - -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(e.data); - if (key.scancode == static_cast(Key::Escape)) { + if (key.keyCode == static_cast(Key::Escape)) { Application::get().quit(); } }); // 鼠标事件 -eventService->addListener(EventType::MousePress, [](Event& e) { - auto& mouse = std::get(e.data); +eventService->addListener(EventType::MouseButtonPressed, [](Event& e) { + auto& mouse = std::get(e.data); // 处理鼠标点击 }); // 手柄事件 -eventService->addListener(EventType::GamepadPress, [](Event& e) { - auto& gamepad = std::get(e.data); +eventService->addListener(EventType::GamepadButtonPressed, [](Event& e) { + auto& gamepad = std::get(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/ # 工具链配置 ``` --- diff --git a/docs/module_system.md b/docs/module_system.md index 0eac41e..5a0fda1 100644 --- a/docs/module_system.md +++ b/docs/module_system.md @@ -2,366 +2,130 @@ ## 概述 -Extra2D 采用模块化架构设计(参考 Kiwano),所有核心功能通过模块系统和服务系统管理。系统提供: +Extra2D 采用模块化架构设计,所有核心功能通过模块系统和服务系统管理。系统提供: -- **自动发现注册**:模块定义即注册,无需手动调用 -- **统一的生命周期管理**:初始化、更新、渲染、关闭 -- **优先级排序**:确保模块按正确顺序初始化 -- **Context 模式**:使用上下文对象遍历模块链 +- **统一的生命周期管理**:初始化、关闭、依赖处理 +- **优先级排序**:确保模块/服务按正确顺序初始化 +- **模块化配置**:每个模块独立管理自己的配置 - **依赖注入**:通过服务定位器解耦模块间依赖 +- **可扩展性**:新增模块无需修改引擎核心代码 ## 架构图 -```mermaid -graph TB - Application["Application
(协调模块和服务)"] - - Application --> ModuleRegistry - Application --> ServiceLocator - - ModuleRegistry["ModuleRegistry
(模块注册器)"] - ServiceLocator["ServiceLocator
(服务定位器)"] - - ModuleRegistry --> ConfigModule["ConfigModule"] - ModuleRegistry --> WindowModule["WindowModule"] - - ServiceLocator --> SceneService["SceneService"] - ServiceLocator --> TimerService["TimerService"] +``` +┌─────────────────────────────────────────────────────────────┐ +│ Application │ +│ (协调模块和服务,通过服务定位器获取依赖) │ +└─────────────────────────────────────────────────────────────┘ + │ + ┌───────────────┴───────────────┐ + ▼ ▼ +┌─────────────────────────────┐ ┌─────────────────────────────┐ +│ ModuleRegistry │ │ ServiceLocator │ +│ (模块注册表,管理平台级模块) │ │ (服务定位器,管理运行时服务) │ +└─────────────────────────────┘ └─────────────────────────────┘ + │ │ + ┌─────┴─────┐ ┌───────┴───────┐ + ▼ ▼ ▼ ▼ +┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ +│ Config │ │ Window │ │ Scene │ │ Timer │ +│ Module │ │ Module │ │ Service │ │ Service │ +└───────────┘ └───────────┘ └───────────┘ └───────────┘ ``` ---- +## 模块化配置系统 -## 模块自动注册 +### 设计原则 -### E2D_MODULE 宏 +Extra2D 采用**模块化配置系统**,遵循开闭原则: -模块通过 `E2D_MODULE` 宏自动注册,无需手动调用任何注册函数: +- **AppConfig** 只包含应用级别配置(appName, appVersion, organization 等) +- **各模块配置** 由模块自己管理,实现 `IModuleConfig` 接口 +- **新增模块** 无需修改引擎核心代码 + +### 配置文件结构 + +``` +Extra2D/include/extra2d/ +├── config/ +│ ├── app_config.h # 应用级别配置 +│ ├── module_config.h # 模块配置接口 +│ └── config_manager.h # 配置管理器 +├── platform/ +│ └── window_module.h # 窗口模块(含 Cfg 配置结构) +├── graphics/ +│ ├── core/ # 渲染核心 +│ │ ├── render_backend.h # 渲染后端接口 +│ │ ├── render_command.h # 渲染命令 +│ │ ├── render_module.h # 渲染模块(含 Cfg 配置结构) +│ │ └── render_target.h # 渲染目标 +│ ├── camera/ # 相机和视口 +│ │ ├── camera.h # 2D 相机 +│ │ └── viewport_adapter.h # 视口适配器 +│ ├── shader/ # Shader 系统 +│ │ ├── shader_interface.h +│ │ ├── shader_manager.h +│ │ ├── shader_loader.h +│ │ ├── shader_cache.h +│ │ ├── shader_hot_reloader.h +│ │ └── shader_preset.h +│ ├── texture/ # 纹理系统 +│ │ ├── texture.h # 纹理接口 +│ │ ├── texture_atlas.h # 纹理图集 +│ │ ├── texture_pool.h # 纹理池 +│ │ ├── alpha_mask.h # Alpha 遮罩 +│ │ └── font.h # 字体接口 +│ ├── memory/ # GPU 内存管理 +│ │ ├── vram_manager.h # VRAM 管理器 +│ │ └── gpu_context.h # GPU 上下文 +│ └── opengl/ # OpenGL 实现 +├── scene/ # 场景系统 +├── services/ # 服务接口 +└── event/ # 事件系统 +``` + +### AppConfig 结构 ```cpp -// config_module.cpp -#include "config_module.h" - -namespace extra2d { -// 模块实现... -} - -// 在文件末尾,namespace 外部 -E2D_MODULE(ConfigModule, 0) // 名称, 优先级 -``` - -### 带依赖的模块 - -```cpp -// window_module.cpp -E2D_MODULE(WindowModule, 20, "ConfigModule") // 依赖 ConfigModule - -// render_module.cpp -E2D_MODULE(RenderModule, 40, "WindowModule", "ConfigModule") // 多个依赖 -``` - -### 带属性的模块 - -```cpp -// render_module.cpp -E2D_MODULE_BEGIN(RenderModule) -E2D_PRIORITY(40) -E2D_DEPENDENCIES("WindowModule") - E2D_PROPERTY(vsync, bool) - E2D_PROPERTY(targetFPS, int) -E2D_MODULE_END() -``` - ---- - -## 链接方式详解 - -### 静态链接 vs 动态链接 - -| 特性 | 静态链接 | 动态链接 | -|------|---------|---------| -| **引擎库** | `.a` / `.lib` | `.dll` / `.so` | -| **模块注册** | 需要 `--whole-archive` 或直接编译 | 自动注册 | -| **自定义模块** | 直接编译到可执行文件 | 编译为独立 DLL | -| **额外代码** | 无 | 需要 `E2D_FORCE_LINK` | -| **分发** | 单一可执行文件 | 需要 DLL 文件 | -| **热更新** | 不支持 | 支持(重新加载 DLL) | - -### 静态链接方案(推荐) - -#### 原理 - -静态链接时,链接器会优化掉未引用的代码。解决方案: -1. **引擎库**:使用 `--whole-archive` 强制链接所有符号 -2. **自定义模块**:直接编译到可执行文件 - -#### xmake 配置 - -```lua --- 引擎库(静态库) -target("extra2d") - set_kind("static") - -- ... 其他配置 - --- 可执行文件 -target("demo_basic") - set_kind("binary") +struct AppConfig { + std::string appName = "Extra2D App"; + std::string appVersion = "1.0.0"; + std::string organization = ""; + std::string configFile = "config.json"; + PlatformType targetPlatform = PlatformType::Auto; - -- 强制链接引擎静态库 - add_ldflags("-Wl,--whole-archive", {force = true}) - add_deps("extra2d") - add_ldflags("-Wl,--no-whole-archive", {force = true}) - - add_files("main.cpp") -``` - -#### 自定义模块配置 - -```lua -target("demo_hello_module") - set_kind("binary") - - -- 强制链接引擎静态库 - add_ldflags("-Wl,--whole-archive", {force = true}) - add_deps("extra2d") - add_ldflags("-Wl,--no-whole-archive", {force = true}) - - -- 直接编译模块源文件到可执行文件 - add_files("main.cpp", "hello_module.cpp") - add_includedirs("hello_module") -``` - -#### 模块定义 - -```cpp -// hello_module.cpp -#include "hello_module.h" - -namespace extra2d { -// 模块实现... -} - -// 在文件末尾,namespace 外部 -E2D_MODULE(HelloModule, 1000) -``` - -#### main.cpp - -```cpp -#include "hello_module.h" -#include - -int main() { - extra2d::Application& app = extra2d::Application::get(); - - // 无需任何注册代码! - // 模块在静态初始化时自动注册 - - extra2d::AppConfig config; - config.appName = "My App"; - - if (!app.init(config)) return 1; - app.run(); - return 0; -} -``` - -### 动态链接方案 - -#### xmake 配置 - -```lua --- 引擎库(动态库) -target("extra2d") - set_kind("shared") - add_defines("E2D_BUILDING_DLL", {public = false}) - -- ... 其他配置 - --- 自定义模块 DLL -target("hello_module_lib") - set_kind("shared") - add_defines("E2D_BUILDING_DLL", {public = false}) - add_deps("extra2d") - add_files("hello_module.cpp") - add_includedirs("hello_module", {public = true}) - --- 可执行文件 -target("demo_hello_module") - set_kind("binary") - add_deps("hello_module_lib") - add_files("main.cpp") -``` - -#### 模块定义 - -```cpp -// hello_module.cpp -#include "hello_module.h" - -namespace extra2d { -// 模块实现... -} - -// 使用 E2D_MODULE_EXPORT(自动生成导出函数) -E2D_MODULE_EXPORT(HelloModule, 1000) -``` - -#### main.cpp - -```cpp -#include "hello_module.h" -#include - -// 声明外部模块的导出函数 -E2D_DECLARE_FORCE_LINK(HelloModule); - -int main() { - // 触发 DLL 加载 - E2D_CALL_FORCE_LINK(HelloModule); - - extra2d::Application& app = extra2d::Application::get(); - if (!app.init(config)) return 1; - app.run(); - return 0; -} -``` - ---- - -## 平台兼容性 - -### Linux - -```lua -add_ldflags("-Wl,--whole-archive", {force = true}) -add_deps("extra2d") -add_ldflags("-Wl,--no-whole-archive", {force = true}) -``` - -### macOS - -macOS 使用 `-force_load` 代替 `--whole-archive`: - -```lua -if is_plat("macosx") then - add_ldflags("-force_load", {force = true}) -end -add_deps("extra2d") -``` - -### Windows (MSVC) - -MSVC 使用 `/WHOLEARCHIVE`: - -```lua -if is_plat("windows") and is_toolchain("msvc") then - add_ldflags("/WHOLEARCHIVE:extra2d", {force = true}) -end -add_deps("extra2d") -``` - -### Windows (MinGW) - -```lua -add_ldflags("-Wl,--whole-archive", {force = true}) -add_deps("extra2d") -add_ldflags("-Wl,--no-whole-archive", {force = true}) -``` - ---- - -## 模块基类 - -### Module - -所有模块只需继承 `Module` 基类,实现需要的生命周期方法: - -```cpp -class Module { -public: - virtual ~Module() = default; - - /** - * @brief 设置模块(初始化) - */ - virtual void setupModule() {} - - /** - * @brief 销毁模块 - */ - virtual void destroyModule() {} - - /** - * @brief 更新时 - */ - virtual void onUpdate(UpdateContext& ctx) { ctx.next(); } - - /** - * @brief 渲染前 - */ - virtual void beforeRender(RenderContext& ctx) { ctx.next(); } - - /** - * @brief 渲染时 - */ - virtual void onRender(RenderContext& ctx) { ctx.next(); } - - /** - * @brief 渲染后 - */ - virtual void afterRender(RenderContext& ctx) { ctx.next(); } - - /** - * @brief 事件处理 - */ - virtual void handleEvent(EventContext& ctx) { ctx.next(); } - - /** - * @brief 获取模块名称 - */ - virtual const char* getName() const = 0; - - /** - * @brief 获取模块优先级 - */ - virtual int getPriority() const { return 0; } + static AppConfig createDefault(); + bool validate() const; + void reset(); + void merge(const AppConfig& other); }; ``` -### 模块上下文 - -用于遍历模块链,支持链式调用: +### 模块配置示例 ```cpp -/** - * @brief 模块上下文基类 - */ -class ModuleContext { -public: - void next(); // 遍历下一个模块 - bool isDone() const; // 检查是否完成 +// window_config.h +struct WindowConfigData { + std::string title = "Extra2D Application"; + int width = 1280; + int height = 720; + WindowMode mode = WindowMode::Windowed; + bool vsync = true; + bool resizable = true; + // ... }; -/** - * @brief 更新上下文 - */ -class UpdateContext : public ModuleContext { +// window_module.h +class WindowModuleConfig : public IModuleConfig { public: - float getDeltaTime() const; // 获取帧间隔时间 -}; - -/** - * @brief 渲染上下文 - */ -class RenderContext : public ModuleContext { -public: - enum class Phase { Before, On, After }; - Phase getPhase() const; -}; - -/** - * @brief 事件上下文 - */ -class EventContext : public ModuleContext { + WindowConfigData windowConfig; + + ModuleInfo getModuleInfo() const override; + std::string getConfigSectionName() const override { return "window"; } + bool validate() const override; + void applyPlatformConstraints(PlatformType platform) override; + bool loadFromJson(const void* jsonData) override; + bool saveToJson(void* jsonData) const override; }; ``` @@ -373,161 +137,283 @@ class EventContext : public ModuleContext { |-----|--------------|---------------| | 用途 | 平台级初始化 | 运行时功能 | | 生命周期 | Application 管理 | ServiceLocator 管理 | -| 注册方式 | 自动发现 | `locator.registerService()` | +| 配置管理 | 独立配置文件 | 无配置 | +| 依赖方式 | 通过 ModuleRegistry | 通过 ServiceLocator | | 可替换性 | 编译时确定 | 运行时可替换 | | 示例 | Window, Render, Input | Scene, Timer, Event, Camera | ---- - ## 模块优先级 模块按优先级从小到大初始化,关闭时逆序执行: -| 模块 | 优先级 | 说明 | -|------|--------|------| -| LoggerModule | -1 | 最先初始化 | -| ConfigModule | 0 | 配置加载 | -| PlatformModule | 10 | 平台初始化 | -| WindowModule | 20 | 窗口创建 | -| InputModule | 30 | 输入系统 | -| RenderModule | 40 | 渲染系统 | -| ScriptModule | 500 | 脚本系统 | -| 用户模块 | 1000+ | 用户自定义 | +| 优先级值 | 枚举名称 | 用途 | 模块示例 | +|---------|---------|------|---------| +| 0 | `Core` | 核心模块,最先初始化 | Config, Platform, Window | +| 50 | `Input` | 输入系统 | Input | +| 100 | `Graphics` | 图形渲染 | Render | +| 200 | `Audio` | 音频系统 | Audio | +| 500 | `Resource` | 资源管理 | Resource | + +--- + +## 模块系统 + +### IModuleConfig + +模块配置接口,定义模块的元数据和配置: + +```cpp +class IModuleConfig { +public: + virtual ~IModuleConfig() = default; + + virtual ModuleInfo getModuleInfo() const = 0; + virtual std::string getConfigSectionName() const = 0; + virtual bool validate() const = 0; + virtual void resetToDefaults() = 0; + virtual bool loadFromJson(const void* jsonData) = 0; + virtual bool saveToJson(void* jsonData) const = 0; + virtual void applyPlatformConstraints(PlatformType platform) {} +}; +``` + +### IModuleInitializer + +模块初始化器接口,管理模块的生命周期: + +```cpp +class IModuleInitializer { +public: + virtual ~IModuleInitializer() = default; + + virtual ModuleId getModuleId() const = 0; + virtual ModulePriority getPriority() const = 0; + virtual std::vector getDependencies() const = 0; + + virtual bool initialize(const IModuleConfig* config) = 0; + virtual void shutdown() = 0; + virtual bool isInitialized() const = 0; +}; +``` + +### ModuleRegistry + +模块注册表,管理所有模块: + +```cpp +class ModuleRegistry { +public: + static ModuleRegistry& instance(); + + ModuleId registerModule( + UniquePtr config, + ModuleInitializerFactory factory + ); + + IModuleConfig* getModuleConfig(ModuleId id); + IModuleInitializer* getInitializer(ModuleId id); + std::vector getAllModules() const; + std::vector getInitializationOrder() const; +}; +``` --- ## 创建新模块 -### 完整示例(静态链接) +### 步骤 1:定义配置数据结构 -**hello_module.h:** ```cpp +// my_module_config.h #pragma once -#include + #include namespace extra2d { -struct HelloModuleConfig { - std::string greeting = "Hello!"; - int repeatCount = 1; +struct MyModuleConfigData { + int someSetting = 42; + bool enabled = true; + std::string path = "default"; }; -class HelloModule : public Module { -public: - HelloModule(); - ~HelloModule() override; - - const char* getName() const override { return "HelloModule"; } - int getPriority() const override { return 1000; } - - void setupModule() override; - void destroyModule() override; - void onUpdate(UpdateContext& ctx) override; - - void setConfig(const HelloModuleConfig& config) { config_ = config; } - void sayHello() const; - -private: - HelloModuleConfig config_; - float time_ = 0.0f; -}; - -} // namespace extra2d +} ``` -**hello_module.cpp:** +### 步骤 2:定义配置类 + ```cpp -#include "hello_module.h" -#include +// my_module.h +#pragma once + +#include +#include +#include "my_module_config.h" namespace extra2d { -HelloModule::HelloModule() = default; - -HelloModule::~HelloModule() { - if (isInitialized()) { - destroyModule(); - } -} - -void HelloModule::setupModule() { - if (isInitialized()) return; +class MyModuleConfig : public IModuleConfig { +public: + MyModuleConfigData config; - setInitialized(true); - E2D_LOG_INFO("HelloModule initialized"); - E2D_LOG_INFO(" Greeting: {}", config_.greeting); - E2D_LOG_INFO(" Repeat Count: {}", config_.repeatCount); - - sayHello(); -} - -void HelloModule::destroyModule() { - if (!isInitialized()) return; - - E2D_LOG_INFO("HelloModule shutdown"); - setInitialized(false); -} - -void HelloModule::onUpdate(UpdateContext& ctx) { - if (!isInitialized()) { - ctx.next(); - return; + ModuleInfo getModuleInfo() const override { + ModuleInfo info; + info.id = 0; + info.name = "MyModule"; + info.version = "1.0.0"; + info.priority = ModulePriority::Graphics; + info.enabled = true; + return info; } - time_ += ctx.getDeltaTime(); + std::string getConfigSectionName() const override { return "my_module"; } + bool validate() const override { return config.someSetting > 0; } - if (time_ >= 5.0f) { - sayHello(); - time_ = 0.0f; + void resetToDefaults() override { + config = MyModuleConfigData{}; } - ctx.next(); -} - -void HelloModule::sayHello() const { - for (int i = 0; i < config_.repeatCount; ++i) { - E2D_LOG_INFO("[HelloModule] {}", config_.greeting); + void applyPlatformConstraints(PlatformType platform) override { +#ifdef __SWITCH__ + config.someSetting = 30; // Switch 平台优化 +#else + (void)platform; +#endif } -} + + bool loadFromJson(const void* jsonData) override; + bool saveToJson(void* jsonData) const override; +}; -} // namespace extra2d - -// 自动注册(在 namespace 外部) -E2D_MODULE(HelloModule, 1000) +} ``` -**main.cpp:** +### 步骤 3:定义初始化器 + ```cpp -#include "hello_module.h" -#include +// my_module.h (续) +class MyModuleInitializer : public IModuleInitializer { +public: + MyModuleInitializer(); + ~MyModuleInitializer() override; + + ModuleId getModuleId() const override { return moduleId_; } + ModulePriority getPriority() const override { return ModulePriority::Graphics; } + std::vector getDependencies() const override; + + bool initialize(const IModuleConfig* config) override; + void shutdown() override; + bool isInitialized() const override { return initialized_; } + + void setModuleId(ModuleId id) { moduleId_ = id; } -int main() { - extra2d::Application& app = extra2d::Application::get(); - - extra2d::AppConfig config; - config.appName = "Hello Module Demo"; - - // 无需手动注册!模块已自动注册 - - if (!app.init(config)) return 1; - app.run(); - return 0; -} +private: + ModuleId moduleId_ = INVALID_MODULE_ID; + bool initialized_ = false; + MyModuleConfigData config_; +}; + +ModuleId get_my_module_id(); +void register_my_module(); ``` -**xmake.lua:** -```lua -target("demo_hello_module") - set_kind("binary") +### 步骤 4:实现模块 + +```cpp +// my_module.cpp +#include "my_module.h" +#include +#include +#include + +using json = nlohmann::json; + +namespace extra2d { + +static ModuleId s_myModuleId = INVALID_MODULE_ID; + +ModuleId get_my_module_id() { return s_myModuleId; } + +bool MyModuleConfig::loadFromJson(const void* jsonData) { + if (!jsonData) return false; + try { + const json& j = *static_cast(jsonData); + if (j.contains("someSetting")) config.someSetting = j["someSetting"].get(); + if (j.contains("enabled")) config.enabled = j["enabled"].get(); + if (j.contains("path")) config.path = j["path"].get(); + return true; + } catch (...) { + return false; + } +} + +bool MyModuleConfig::saveToJson(void* jsonData) const { + if (!jsonData) return false; + try { + json& j = *static_cast(jsonData); + j["someSetting"] = config.someSetting; + j["enabled"] = config.enabled; + j["path"] = config.path; + return true; + } catch (...) { + return false; + } +} + +MyModuleInitializer::MyModuleInitializer() : moduleId_(INVALID_MODULE_ID), initialized_(false) {} + +MyModuleInitializer::~MyModuleInitializer() { if (initialized_) shutdown(); } + +std::vector MyModuleInitializer::getDependencies() const { + return {}; // 无依赖 +} + +bool MyModuleInitializer::initialize(const IModuleConfig* config) { + if (initialized_) return true; - -- 强制链接引擎静态库 - add_ldflags("-Wl,--whole-archive", {force = true}) - add_deps("extra2d") - add_ldflags("-Wl,--no-whole-archive", {force = true}) + const MyModuleConfig* cfg = dynamic_cast(config); + if (!cfg) { + E2D_LOG_ERROR("Invalid MyModule config"); + return false; + } - -- 直接编译模块源文件 - add_files("main.cpp", "hello_module.cpp") + config_ = cfg->config; + + // 执行初始化逻辑... + + initialized_ = true; + E2D_LOG_INFO("MyModule initialized with setting: {}", config_.someSetting); + return true; +} + +void MyModuleInitializer::shutdown() { + if (!initialized_) return; + // 执行清理逻辑... + initialized_ = false; + E2D_LOG_INFO("MyModule shutdown"); +} + +void register_my_module() { + if (s_myModuleId != INVALID_MODULE_ID) return; + + s_myModuleId = ModuleRegistry::instance().registerModule( + makeUnique(), + []() -> UniquePtr { + auto initializer = makeUnique(); + initializer->setModuleId(s_myModuleId); + return initializer; + } + ); +} + +namespace { + struct MyModuleAutoRegister { + MyModuleAutoRegister() { register_my_module(); } + }; + static MyModuleAutoRegister s_autoRegister; +} + +} ``` --- @@ -538,23 +424,55 @@ target("demo_hello_module") **职责**:管理 ConfigManager 和应用配置 -**优先级**:0 - +**配置**: ```cpp -extra2d::AppConfig config; +AppConfig config; config.appName = "My Application"; config.appVersion = "1.0.0"; ``` --- +### Platform 模块 + +**职责**:平台检测和平台特定初始化 + +**支持平台**: +- Windows +- Linux +- macOS +- Nintendo Switch + +**平台能力查询**: +```cpp +auto* platformConfig = createPlatformConfig(); +const auto& caps = platformConfig->capabilities(); +if (caps.supportsGamepad) { + // 支持手柄 +} +``` + +--- + ### Window 模块 **职责**:窗口创建和管理 -**优先级**:20 +**后端**:统一使用 SDL2,支持所有平台 -**后端**:统一使用 SDL2 +**配置**: +```cpp +WindowModule::Cfg cfg; +cfg.title = "My App"; +cfg.w = 1280; +cfg.h = 720; +cfg.mode = WindowMode::Windowed; +cfg.vsync = true; +cfg.backend = "sdl2"; // 可选:指定后端 +``` + +**平台约束**: +- Switch 平台自动强制全屏模式 --- @@ -562,7 +480,36 @@ config.appVersion = "1.0.0"; **职责**:输入设备管理(键盘、鼠标、手柄) -**优先级**:30 +**配置**: +```cpp +InputModule::Cfg cfg; +cfg.deadzone = 0.15f; +cfg.mouseSensitivity = 1.0f; +cfg.enableVibration = true; +``` + +**使用示例**: +```cpp +IInput* input = app.window().input(); + +// 键盘 +if (input->pressed(Key::Space)) { + // 空格键刚按下 +} + +// 鼠标 +if (input->down(Mouse::Left)) { + Vec2 pos = input->mouse(); +} + +// 手柄 +if (input->gamepad()) { + Vec2 stick = input->leftStick(); + if (input->pressed(Gamepad::A)) { + input->vibrate(0.5f, 0.5f); // 振动反馈 + } +} +``` --- @@ -570,7 +517,46 @@ config.appVersion = "1.0.0"; **职责**:渲染器初始化和管理 -**优先级**:40 +**配置**: +```cpp +RenderModule::Cfg cfg; +cfg.backend = BackendType::OpenGL; +cfg.vsync = true; +cfg.targetFPS = 60; +cfg.multisamples = 4; +``` + +--- + +## 配置文件格式 + +配置使用 JSON 格式,每个模块有独立的配置节: + +```json +{ + "app": { + "name": "My Application", + "version": "1.0.0", + "organization": "MyCompany" + }, + "window": { + "title": "My Application", + "width": 1280, + "height": 720, + "mode": "windowed", + "vsync": true + }, + "render": { + "targetFPS": 60, + "multisamples": 4 + }, + "input": { + "deadzone": 0.15, + "mouseSensitivity": 1.0, + "enableVibration": true + } +} +``` --- @@ -578,7 +564,7 @@ config.appVersion = "1.0.0"; ### IService -服务接口基类: +服务接口基类,所有服务必须实现: ```cpp class IService { @@ -589,6 +575,10 @@ public: virtual bool initialize() = 0; virtual void shutdown() = 0; virtual void update(float deltaTime); + virtual bool isInitialized() const; + + ServiceState getState() const; + const std::string& getName() const; }; ``` @@ -596,21 +586,18 @@ public: | 服务 | 用途 | 优先级 | |-----|------|-------| -| EventService | 事件分发 | 100 | -| TimerService | 计时器 | 200 | | SceneService | 场景管理 | 300 | +| TimerService | 计时器 | 200 | +| EventService | 事件分发 | 100 | | CameraService | 相机系统 | 400 | ### 使用服务 ```cpp -auto& app = extra2d::Application::get(); - // 获取服务 -auto sceneService = app.scenes(); -auto timerService = app.timers(); -auto eventService = app.events(); -auto cameraService = app.camera(); +auto sceneService = Application::get().scenes(); +auto timerService = Application::get().timers(); +auto eventService = Application::get().events(); // 使用场景服务 sceneService->pushScene(myScene); @@ -621,8 +608,8 @@ timerService->addTimer(1.0f, []() { }); // 使用事件服务 -eventService->addListener(extra2d::EventType::KeyPress, [](extra2d::Event& e) { - auto& keyEvent = std::get(e.data); +eventService->addListener(EventType::KeyPressed, [](Event& e) { + auto& keyEvent = std::get(e.data); E2D_LOG_INFO("Key pressed: {}", keyEvent.keyCode); }); ``` @@ -636,74 +623,210 @@ eventService->addListener(extra2d::EventType::KeyPress, [](extra2d::Event& e) { ```cpp enum class EventType { // 键盘 - KeyPress, - KeyRelease, + KeyPressed, + KeyReleased, KeyRepeat, // 鼠标 - MousePress, - MouseRelease, - MouseMove, - MouseScroll, + MouseButtonPressed, + MouseButtonReleased, + MouseMoved, + MouseScrolled, // 手柄 - GamepadConnect, - GamepadDisconnect, - GamepadPress, - GamepadRelease, + GamepadConnected, + GamepadDisconnected, + GamepadButtonPressed, + GamepadButtonReleased, // 触摸 - TouchBegin, - TouchMove, - TouchEnd, + TouchBegan, + TouchMoved, + TouchEnded, // 窗口 WindowResize, WindowClose, + // ... }; ``` ### 事件监听 ```cpp -auto eventService = app.events(); +auto eventService = Application::get().events(); // 监听键盘事件 -eventService->addListener(EventType::KeyPress, [](Event& e) { +eventService->addListener(EventType::KeyPressed, [](Event& e) { auto& key = std::get(e.data); - if (key.scancode == static_cast(Key::Escape)) { - Application::get().quit(); - } + E2D_LOG_INFO("Key: {}, mods: {}", key.keyCode, key.mods); }); // 监听鼠标事件 -eventService->addListener(EventType::MousePress, [](Event& e) { - auto& mouse = std::get(e.data); - E2D_LOG_INFO("Click at ({}, {})", mouse.position.x, mouse.position.y); +eventService->addListener(EventType::MouseButtonPressed, [](Event& e) { + auto& mouse = std::get(e.data); + E2D_LOG_INFO("Mouse button: {} at ({}, {})", + mouse.button, mouse.position.x, mouse.position.y); }); ``` --- +## 平台支持 + +### 支持的平台 + +| 平台 | 窗口后端 | 图形 API | 特殊处理 | +|-----|---------|---------|---------| +| Windows | SDL2 | OpenGL ES 3.2 | - | +| Linux | SDL2 | OpenGL ES 3.2 | - | +| macOS | SDL2 | OpenGL ES 3.2 | - | +| Nintendo Switch | SDL2 | OpenGL ES 3.2 | romfs, 强制全屏 | + +### 平台检测 + +```cpp +PlatformType platform = PlatformDetector::detect(); +const char* name = getPlatformTypeName(platform); + +switch (platform) { + case PlatformType::Windows: // Windows 处理 + case PlatformType::Switch: // Switch 处理 + // ... +} +``` + +### 平台能力 + +```cpp +auto* config = createPlatformConfig(); +const auto& caps = config->capabilities(); + +if (caps.supportsWindowed) { /* 支持窗口模式 */ } +if (caps.supportsGamepad) { /* 支持手柄 */ } +if (caps.supportsTouch) { /* 支持触摸 */ } +``` + +--- + +## 最佳实践 + +### 1. 模块配置独立化 + +```cpp +// 好的做法:模块管理自己的配置 +class WindowModule : public Module { + Cfg cfg_; // 模块内部配置 +public: + struct Cfg { + std::string title = "Extra2D"; + int w = 1280; + int h = 720; + WindowMode mode = WindowMode::Windowed; + bool vsync = true; + int priority = 0; + }; +}; + +// 不好的做法:所有配置放在 AppConfig +struct AppConfig { + WindowConfig window; // 耦合度高 + RenderConfig render; + // ... 新增模块需要修改 AppConfig +}; +``` + +### 2. 使用平台约束 + +```cpp +void WindowModuleConfig::applyPlatformConstraints(PlatformType platform) { +#ifdef __SWITCH__ + windowConfig.mode = WindowMode::Fullscreen; + windowConfig.resizable = false; +#else + (void)platform; +#endif +} +``` + +### 3. 模块自动注册 + +```cpp +namespace { + struct MyModuleAutoRegister { + MyModuleAutoRegister() { register_my_module(); } + }; + static MyModuleAutoRegister s_autoRegister; // 程序启动时自动注册 +} +``` + +--- + +## 调试 + +### 查看模块初始化顺序 + +```cpp +auto order = ModuleRegistry::instance().getInitializationOrder(); +for (ModuleId id : order) { + auto* config = ModuleRegistry::instance().getModuleConfig(id); + if (config) { + auto info = config->getModuleInfo(); + E2D_LOG_INFO("Module: {} (priority: {})", + info.name, static_cast(info.priority)); + } +} +``` + +### 查看服务状态 + +```cpp +auto services = ServiceLocator::instance().getAllServices(); +for (const auto& service : services) { + auto info = service->getServiceInfo(); + E2D_LOG_INFO("Service: {} (state: {})", + info.name, static_cast(info.state)); +} +``` + +--- + ## 场景图系统 +### 概述 + +Extra2D 使用场景图(Scene Graph)管理游戏对象。场景图是一个树形结构,每个节点可以包含子节点,形成层级关系。 + ### Node 基类 +所有场景对象的基类,提供变换、层级管理和渲染功能: + ```cpp class Node : public std::enable_shared_from_this { public: // 层级管理 void addChild(Ptr child); void removeChild(Ptr child); + void detach(); + void clearChildren(); + Ptr getParent() const; + const std::vector>& getChildren() const; + Ptr findChild(const std::string& name) const; // 变换属性 void setPos(const Vec2& pos); void setRotation(float degrees); void setScale(const Vec2& scale); + void setAnchor(const Vec2& anchor); + void setOpacity(float opacity); + void setVisible(bool visible); + void setZOrder(int zOrder); // 世界变换 Vec2 toWorld(const Vec2& localPos) const; + Vec2 toLocal(const Vec2& worldPos) const; + glm::mat4 getLocalTransform() const; glm::mat4 getWorldTransform() const; // 生命周期回调 @@ -714,111 +837,381 @@ public: }; ``` +### Scene 类 + +场景是场景图的根节点,管理相机和视口: + +```cpp +class Scene : public Node { +public: + // 场景属性 + void setBackgroundColor(const Color& color); + + // 摄像机 + void setCamera(Ptr camera); + Camera* getActiveCamera() const; + + // 视口 + void setViewportSize(float width, float height); + + // 渲染和更新 + void renderScene(RenderBackend& renderer); + void updateScene(float dt); + + // 静态创建 + static Ptr create(); +}; +``` + ### ShapeNode 形状节点 +用于绘制几何形状: + ```cpp // 创建形状节点 -auto rect = ShapeNode::createFilledRect( - Rect(0, 0, 100, 100), - Color(1.0f, 0.4f, 0.4f, 1.0f) -); - -auto circle = ShapeNode::createFilledCircle( - Vec2(0, 0), 50, - Color(0.4f, 0.4f, 1.0f, 1.0f) -); - +auto rect = ShapeNode::createFilledRect(Rect(0, 0, 100, 100), Color(1.0f, 0.4f, 0.4f, 1.0f)); +auto circle = ShapeNode::createFilledCircle(Vec2(0, 0), 50, Color(0.4f, 0.4f, 1.0f, 1.0f)); auto triangle = ShapeNode::createFilledTriangle( Vec2(0, -40), Vec2(-35, 30), Vec2(35, 30), Color(0.4f, 1.0f, 0.4f, 1.0f) ); +auto line = ShapeNode::createLine(Vec2(0, 0), Vec2(100, 100), Color(1.0f, 1.0f, 1.0f, 1.0f), 2.0f); +auto polygon = ShapeNode::createFilledPolygon( + {Vec2(0, -50), Vec2(50, 0), Vec2(0, 50), Vec2(-50, 0)}, + Color(1.0f, 0.4f, 1.0f, 1.0f) +); +``` + +### 变换继承 + +子节点继承父节点的变换: + +```cpp +auto parent = makeShared(); +parent->setPos(100, 100); +parent->setRotation(45); // 旋转 45 度 + +auto child = makeShared(); +child->setPos(50, 0); // 相对于父节点的位置 +parent->addChild(child); + +// child 的世界位置 = parent 的变换 * child 的本地位置 +// child 会随 parent 一起旋转 +``` + +### 渲染流程 + +``` +Application::render() + └── CameraService::getViewProjectionMatrix() // 设置视图投影矩阵 + └── SceneService::render() + └── Scene::renderContent() + └── Node::render() (递归) + └── pushTransform(localTransform) // 压入本地变换 + └── onDraw() // 绘制形状 + └── children::onRender() // 递归渲染子节点 + └── popTransform() // 弹出变换 ``` --- -## 常见问题 +## 视口适配系统 -### Q: 模块没有被注册? +### 概述 -**静态链接:** -- 确保使用了 `--whole-archive` -- 自定义模块要直接编译到可执行文件 +视口适配系统确保游戏内容在不同分辨率和宽高比的屏幕上正确显示。 -**动态链接:** -- 确保使用了 `E2D_MODULE_EXPORT` -- 确保调用了 `E2D_CALL_FORCE_LINK` +### ViewportAdapter -### Q: 链接错误 "undefined reference"? +视口适配器,计算视口位置和缩放: -检查链接顺序,`--whole-archive` 要在 `add_deps` 之前。 +```cpp +// 视口适配模式 +enum class ViewportMode { + AspectRatio, // 保持宽高比,可能有黑边 + Stretch, // 拉伸填满整个窗口 + Center, // 居中显示,不缩放 + Custom // 自定义缩放和偏移 +}; -### Q: 模块初始化顺序错误? - -使用 `getPriority()` 控制顺序,数字小的先初始化。 - -### Q: 如何调试模块注册? - -查看日志输出: +// 视口配置 +struct ViewportConfig { + float logicWidth = 1920.0f; + float logicHeight = 1080.0f; + ViewportMode mode = ViewportMode::AspectRatio; + Color letterboxColor = Colors::Black; // 黑边颜色 + float customScale = 1.0f; + Vec2 customOffset = Vec2::Zero(); +}; ``` -[INFO ] ModuleRegistry: 4 modules registered -[INFO ] - ConfigModule (priority: 0) -[INFO ] - WindowModule (priority: 20) -[INFO ] - InputModule (priority: 30) -[INFO ] - RenderModule (priority: 40) + +### 使用 CameraService 配置视口 + +```cpp +auto cameraService = app.camera(); +if (cameraService) { + ViewportConfig vpConfig; + vpConfig.logicWidth = 1280.0f; // 逻辑分辨率宽度 + vpConfig.logicHeight = 720.0f; // 逻辑分辨率高度 + vpConfig.mode = ViewportMode::AspectRatio; // 保持宽高比 + vpConfig.letterboxColor = Color(0.0f, 0.0f, 0.0f, 1.0f); // 黑边颜色 + + cameraService->setViewportConfig(vpConfig); + cameraService->updateViewport(windowWidth, windowHeight); + cameraService->applyViewportAdapter(); +} ``` +### 窗口大小变化处理 + +当窗口大小变化时,Application 会自动更新视口: + +```cpp +// Application 内部处理 +window_->onResize([this, cameraService](int width, int height) { + cameraService->updateViewport(width, height); + cameraService->applyViewportAdapter(); + + auto sceneService = ServiceLocator::instance().getService(); + if (sceneService) { + auto currentScene = sceneService->getCurrentScene(); + if (currentScene) { + currentScene->setViewportSize(width, height); + } + } +}); +``` + +### 适配模式对比 + +| 模式 | 描述 | 适用场景 | +|-----|------|---------| +| `AspectRatio` | 保持宽高比,可能有黑边 | 大多数游戏 | +| `Stretch` | 拉伸填满整个窗口 | 不在乎变形的简单游戏 | +| `Center` | 居中显示,不缩放 | 固定分辨率的像素游戏 | +| `Custom` | 自定义缩放和偏移 | 特殊需求 | + --- ## 示例 完整示例请参考: -- [examples/hello_module/](../../examples/hello_module/) - 自定义模块示例 -- [examples/basic/main.cpp](../../examples/basic/main.cpp) - 基础示例 +- [examples/hello_module/](../../examples/hello_module/) - **Hello World 自定义模块示例** +- [examples/basic/main.cpp](../../examples/basic/main.cpp) - 基础示例(场景图、输入事件、视口适配) +- [Extra2D/src/platform/window_module.cpp](../../Extra2D/src/platform/window_module.cpp) - Window 模块实现 +- [Extra2D/src/platform/input_module.cpp](../../Extra2D/src/platform/input_module.cpp) - Input 模块实现 +- [Extra2D/src/graphics/core/render_module.cpp](../../Extra2D/src/graphics/core/render_module.cpp) - Render 模块实现 +- [Extra2D/src/scene/node.cpp](../../Extra2D/src/scene/node.cpp) - Node 实现 +- [Extra2D/src/scene/shape_node.cpp](../../Extra2D/src/scene/shape_node.cpp) - ShapeNode 实现 --- -## 最佳实践 +## Hello World 自定义模块示例 -### 1. 模块优先级 +### 示例概述 + +`examples/hello_module/` 目录包含一个完整的自定义模块示例,展示如何: + +1. 定义模块配置数据结构 +2. 实现 `IModuleConfig` 接口 +3. 实现 `IModuleInitializer` 接口 +4. 使用自动注册机制 +5. 支持 JSON 配置 + +### 文件结构 + +``` +examples/hello_module/ +├── hello_module.h # 模块头文件(配置类 + 初始化器类) +├── hello_module.cpp # 模块实现 +├── main.cpp # 示例入口 +└── config.json # 配置文件示例 +``` + +### 核心代码解析 + +#### 1. 配置数据结构 ```cpp -// 核心模块使用低优先级 -class LoggerModule : public Module { - int getPriority() const override { return -1; } -}; - -// 用户模块使用高优先级 -class MyModule : public Module { - int getPriority() const override { return 1000; } +struct HelloModuleConfigData { + std::string greeting = "Hello, Extra2D!"; + int repeatCount = 1; + bool enableLogging = true; }; ``` -### 2. 链式调用 +#### 2. 配置类实现 ```cpp -void onUpdate(UpdateContext& ctx) override { - // 执行更新逻辑 - doSomething(); - - // 继续下一个模块 - ctx.next(); -} -``` +class HelloModuleConfig : public IModuleConfig { +public: + HelloModuleConfigData config; -### 3. 检查初始化状态 - -```cpp -void onUpdate(UpdateContext& ctx) override { - if (!isInitialized()) { - ctx.next(); - return; + ModuleInfo getModuleInfo() const override { + ModuleInfo info; + info.name = "HelloModule"; + info.version = "1.0.0"; + info.priority = ModulePriority::User; // 用户自定义模块 + return info; } + + std::string getConfigSectionName() const override { + return "hello"; // 对应 config.json 中的 "hello" 节 + } + + bool validate() const override { + return !config.greeting.empty() && config.repeatCount > 0; + } +}; +``` + +#### 3. 初始化器实现 + +```cpp +class HelloModuleInitializer : public IModuleInitializer { +public: + bool initialize(const IModuleConfig* config) override { + const HelloModuleConfig* cfg = dynamic_cast(config); + if (!cfg || !cfg->validate()) { + return false; + } + + config_ = cfg->config; + initialized_ = true; + + // 执行模块初始化逻辑 + E2D_LOG_INFO("HelloModule initialized: {}", config_.greeting); + return true; + } + + void shutdown() override { + E2D_LOG_INFO("HelloModule shutdown"); + initialized_ = false; + } +}; +``` + +#### 4. 自动注册机制 + +```cpp +namespace { + struct HelloModuleAutoRegister { + HelloModuleAutoRegister() { + register_hello_module(); + } + }; - // 执行更新逻辑 - ctx.next(); + static HelloModuleAutoRegister s_autoRegister; // 程序启动时自动执行 } ``` -### 4. 静态链接优先 +### 配置文件示例 -静态链接更简单,无需额外配置,推荐用于大多数场景。 +```json +{ + "hello": { + "greeting": "Hello from custom module!", + "repeatCount": 3, + "enableLogging": true + } +} +``` + +### 运行示例 + +```bash +xmake run demo_hello_module +``` + +### 预期输出 + +``` +[INFO] HelloModule initialized +[INFO] Greeting: Hello, Extra2D! +[INFO] Repeat Count: 1 +[INFO] Logging Enabled: true +[INFO] [HelloModule] Hello, Extra2D! +[INFO] HelloScene entered +[INFO] [HelloModule] Hello, Extra2D! # 场景 onEnter() 调用 +[INFO] Scene calling HelloModule from onUpdate... +[INFO] [HelloModule] Hello, Extra2D! # 场景每5秒调用 +[INFO] HelloModule shutdown - Goodbye! +``` + +### 在场景中使用模块 + +模块初始化后,可以在场景中通过 `ModuleRegistry` 获取模块实例并调用其功能: + +```cpp +class HelloScene : public Scene { +public: + void onEnter() override { + Scene::onEnter(); + + // 获取模块初始化器 + ModuleId helloId = get_hello_module_id(); + auto* initializer = ModuleRegistry::instance().getInitializer(helloId); + + if (initializer) { + // 转换为具体类型 + auto* helloInit = dynamic_cast(initializer); + if (helloInit) { + // 调用模块功能 + helloInit->sayHello(); + } + } + } + + void onUpdate(float dt) override { + Scene::onUpdate(dt); + + time_ += dt; + + // 每5秒调用一次模块功能 + if (time_ >= 5.0f) { + ModuleId helloId = get_hello_module_id(); + auto* initializer = ModuleRegistry::instance().getInitializer(helloId); + if (initializer) { + auto* helloInit = dynamic_cast(initializer); + if (helloInit) { + helloInit->sayHello(); + } + } + time_ = 0.0f; + } + } + +private: + float time_ = 0.0f; +}; +``` + +### 模块使用流程总结 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 1. 程序启动 │ +│ └── 静态变量 HelloModuleAutoRegister 自动注册模块 │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 2. Application::init() │ +│ └── 遍历 ModuleRegistry 中所有已注册模块 │ +│ └── 按优先级顺序调用 initialize() │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 3. 场景/其他代码使用模块 │ +│ └── ModuleRegistry::getInitializer(moduleId) │ +│ └── dynamic_cast 转换为具体类型 │ +│ └── 调用模块方法 │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 4. Application::shutdown() │ +│ └── 按逆序调用所有模块的 shutdown() │ +└─────────────────────────────────────────────────────────────┘ +``` diff --git a/examples/basic/main.cpp b/examples/basic/main.cpp index 04b7f72..9227865 100644 --- a/examples/basic/main.cpp +++ b/examples/basic/main.cpp @@ -1,24 +1,19 @@ /** * @file main.cpp * @brief Extra2D 场景图测试示例 - * - * 演示场景图功能: - * - 节点层级关系 - * - 变换(位置、旋转、缩放) - * - 形状节点渲染 - * - 输入事件处理 - * - * 模块现在通过 E2D_MODULE 宏自动注册,无需手动调用 app.use() */ #include +#include +#include +#include +#include +#include +#include #include 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(winCfg); - if (!app.init(config)) { + RenderModule::Cfg renderCfg; + renderCfg.priority = 10; + app.use(renderCfg); + + InputModule::Cfg inputCfg; + inputCfg.priority = 20; + app.use(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(); if (eventService) { - eventService->addListener(EventType::KeyPress, [](Event &e) { + eventService->addListener(EventType::KeyPressed, [](Event &e) { auto &keyEvent = std::get(e.data); - - if (keyEvent.scancode == static_cast(Key::Escape)) { + if (keyEvent.keyCode == static_cast(Key::Escape)) { e.handled = true; Application::get().quit(); } }); - eventService->addListener(EventType::MousePress, [](Event &e) { - auto &mouseEvent = std::get(e.data); + eventService->addListener(EventType::MouseButtonPressed, [](Event &e) { + auto &mouseEvent = std::get(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(app.window().width()), - static_cast(app.window().height())); + if (win) { + scene->setViewportSize(static_cast(win->width()), + static_cast(win->height())); + } - auto cameraService = app.camera(); - if (cameraService) { + auto cameraService = ServiceLocator::instance().getService(); + if (cameraService && win) { ViewportConfig vpConfig; - vpConfig.logicWidth = static_cast(app.window().width()); - vpConfig.logicHeight = static_cast(app.window().height()); + vpConfig.logicWidth = static_cast(win->width()); + vpConfig.logicHeight = static_cast(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; } diff --git a/examples/hello_module/hello_module.cpp b/examples/hello_module/hello_module.cpp index f91896f..6bb6172 100644 --- a/examples/hello_module/hello_module.cpp +++ b/examples/hello_module/hello_module.cpp @@ -1,82 +1,38 @@ #include "hello_module.h" -#include -#include +#include 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) diff --git a/examples/hello_module/hello_module.h b/examples/hello_module/hello_module.h index 4f87fb0..253e910 100644 --- a/examples/hello_module/hello_module.h +++ b/examples/hello_module/hello_module.h @@ -1,78 +1,50 @@ #pragma once -#include #include #include 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 diff --git a/examples/hello_module/main.cpp b/examples/hello_module/main.cpp index 37b5f16..8d1a649 100644 --- a/examples/hello_module/main.cpp +++ b/examples/hello_module/main.cpp @@ -1,61 +1,82 @@ #include "hello_module.h" -#include +#include +#include +#include +#include +#include +#include using namespace extra2d; -/** - * @brief 自定义场景类 - * - * 展示如何在场景中使用自定义模块 - */ class HelloScene : public Scene { public: static Ptr create() { return makeShared(); } void onEnter() override { Scene::onEnter(); - addListener(EventType::KeyPress, [](Event &e) { - auto &keyEvent = std::get(e.data); - auto app = Application::get().getModule(); - E2D_LOG_INFO("Module {}", app->getName()); - if (keyEvent.scancode == static_cast(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(); + 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(); + 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::Cfg{.w = 800, .h = 600}); + app.use(); + app.use(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; }