From f9be301dae459cd765e4df69f66d41d73feaaaca Mon Sep 17 00:00:00 2001 From: ChestnutYueyue <952134128@qq.com> Date: Sat, 28 Feb 2026 23:35:34 +0800 Subject: [PATCH] =?UTF-8?q?refactor(=E5=BC=95=E6=93=8E=E6=A0=B8=E5=BF=83):?= =?UTF-8?q?=20=E9=87=8D=E6=9E=84=E6=A8=A1=E5=9D=97=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E5=B9=B6=E5=BC=95=E5=85=A5=E8=87=AA=E5=8A=A8=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构引擎核心模块系统,使用新的 Module 基类替代旧版 IModule 接口 新增模块自动注册机制,通过 E2D_REGISTER_MODULE 宏实现模块注册 将窗口、文件、定时器等模块迁移到新系统,支持配置事件驱动初始化 移除旧版 SDL2 封装和模块管理器,简化应用程序初始化流程 --- include/app/application.h | 56 ++- include/config/app_config.h | 27 ++ include/config/window_config.h | 24 ++ include/event/event_bus_macros.h | 28 ++ include/event/events.h | 14 +- include/extra2d.h | 4 + include/module/module.h | 45 +++ include/module/module_registry.h | 150 +++---- include/module/timer_module.h | 149 ------- include/platform/{file.h => file_module.h} | 14 +- include/platform/input.h | 378 ------------------ include/platform/input_module.h | 376 +++++++++++++++++ include/platform/sdl2.h | 59 --- include/platform/window.h | 168 -------- include/platform/window_module.h | 181 +++++++++ include/utils/timer_module.h | 143 +++++++ src/app/application.cpp | 226 +++++------ src/module/module_registry.cpp | 156 +------- src/platform/file.cpp | 212 ---------- src/platform/file_module.cpp | 251 ++++++++++++ src/platform/{input.cpp => input_module.cpp} | 6 +- src/platform/sdl2.cpp | 66 --- .../{window.cpp => window_module.cpp} | 6 +- xmake/engine.lua | 2 +- 24 files changed, 1323 insertions(+), 1418 deletions(-) create mode 100644 include/config/app_config.h create mode 100644 include/config/window_config.h create mode 100644 include/module/module.h delete mode 100644 include/module/timer_module.h rename include/platform/{file.h => file_module.h} (91%) delete mode 100644 include/platform/input.h create mode 100644 include/platform/input_module.h delete mode 100644 include/platform/sdl2.h delete mode 100644 include/platform/window.h create mode 100644 include/platform/window_module.h create mode 100644 include/utils/timer_module.h delete mode 100644 src/platform/file.cpp create mode 100644 src/platform/file_module.cpp rename src/platform/{input.cpp => input_module.cpp} (99%) delete mode 100644 src/platform/sdl2.cpp rename src/platform/{window.cpp => window_module.cpp} (96%) diff --git a/include/app/application.h b/include/app/application.h index baaf8fc..57d0ae3 100644 --- a/include/app/application.h +++ b/include/app/application.h @@ -1,8 +1,9 @@ #pragma once -#include -#include +#include +#include #include +#include namespace extra2d { @@ -12,27 +13,10 @@ class WindowModule; class InputModule; /** - * @brief 应用程序配置 - */ -struct AppConfig { - std::string title = "Extra2D Application"; - int32 width = 1280; - int32 height = 720; - bool fullscreen = false; - bool resizable = true; - bool vsync = true; - int32 fpsLimit = 0; - int32 glMajor = 3; - int32 glMinor = 3; - bool enableCursors = true; - bool enableDpiScale = false; -}; - -/** - * @brief 应用程序主控类 + * @brief 应用程序主控类 - 简化版 * * 管理应用程序生命周期、窗口和主循环 - * 基于新的 Context 架构,支持多实例 + * 自动管理模块的创建和销毁 */ class Application { public: @@ -102,7 +86,7 @@ public: /** * @brief 获取当前 FPS */ - int32 fps() const { return currentFps_; } + int32 fps() const { return 0; } // TODO: 使用 SDL 计算 FPS /** * @brief 获取配置 @@ -117,12 +101,27 @@ public: /** * @brief 获取窗口模块 */ - WindowModule* getWindow() const { return windowModule_.get(); } + WindowModule* getWindow() const; /** * @brief 获取输入模块 */ - InputModule* getInput() const { return inputModule_.get(); } + InputModule* getInput() const; + + /** + * @brief 获取指定类型的模块 + * @tparam T 模块类型 + * @return 模块指针,未找到返回 nullptr + */ + template + T* getModule() const { + for (const auto& module : modules_) { + if (auto* ptr = dynamic_cast(module.get())) { + return ptr; + } + } + return nullptr; + } /** * @brief 获取窗口宽度 @@ -142,12 +141,11 @@ public: private: Application(); - void mainLoop(); void update(); + void initModules(); std::unique_ptr context_; - std::unique_ptr windowModule_; - std::unique_ptr inputModule_; + std::vector> modules_; AppConfig config_; @@ -158,10 +156,6 @@ private: float deltaTime_ = 0.0f; float totalTime_ = 0.0f; - double lastFrameTime_ = 0.0; - int32 frameCount_ = 0; - float fpsTimer_ = 0.0f; - int32 currentFps_ = 0; }; } // namespace extra2d diff --git a/include/config/app_config.h b/include/config/app_config.h new file mode 100644 index 0000000..0d242c7 --- /dev/null +++ b/include/config/app_config.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace extra2d { + +/** + * @brief 应用程序配置 + * + * 包含应用程序和窗口的初始化配置 + */ +struct AppConfig { + std::string title = "Extra2D Application"; // 窗口标题 + int32 width = 1280; // 窗口宽度 + int32 height = 720; // 窗口高度 + bool fullscreen = false; // 是否全屏 + bool resizable = true; // 是否可调整大小 + bool vsync = true; // 是否垂直同步 + int32 fpsLimit = 0; // FPS限制(0表示不限制) + int32 glMajor = 3; // OpenGL主版本 + int32 glMinor = 3; // OpenGL次版本 + bool enableCursors = true; // 启用光标 + bool enableDpiScale = false; // 启用DPI缩放 +}; + +} // namespace extra2d diff --git a/include/config/window_config.h b/include/config/window_config.h new file mode 100644 index 0000000..555d5cf --- /dev/null +++ b/include/config/window_config.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +namespace extra2d { + +/** + * @brief 窗口配置 + * + * 专用于窗口模块的配置结构体 + */ +struct WindowCfg { + std::string title = "Extra2D"; // 窗口标题 + int32 width = 1280; // 窗口宽度 + int32 height = 720; // 窗口高度 + bool fullscreen = false; // 是否全屏 + bool resizable = true; // 是否可调整大小 + bool vsync = true; // 是否垂直同步 + int32 glMajor = 3; // OpenGL主版本 + int32 glMinor = 3; // OpenGL次版本 +}; + +} // namespace extra2d diff --git a/include/event/event_bus_macros.h b/include/event/event_bus_macros.h index 9fc1bed..4e736ef 100644 --- a/include/event/event_bus_macros.h +++ b/include/event/event_bus_macros.h @@ -160,3 +160,31 @@ arg5, arg6, arg7); \ } \ }; + +/** + * @brief 声明模板事件(支持任意配置类型) + * @param EventName 事件名称 + * @param BusName 总线名称 + * + * 使用示例: + * DECLARE_EVENT_T(OnModuleInit, Engine) + * + * // 发送事件 + * OnModuleInit::emit(myConfig); + * + * // 监听事件 + * OnModuleInit::subscribe(this, &MyModule::onInit); + */ +#define DECLARE_EVENT_T(EventName, BusName) \ + template \ + struct EventName \ + : ::extra2d::event::EventTrait { \ + using Listener = ::extra2d::event::Listener>; \ + static constexpr const char *NAME = #EventName; \ + static constexpr const char *BUS_NAME = \ + E2D_EVENT_BUS_NAME_(BusName)::NAME; \ + static void emit(const ConfigT &config) { \ + ::extra2d::event::broadcast>(config); \ + } \ + }; diff --git a/include/event/events.h b/include/event/events.h index 00913bb..1343c42 100644 --- a/include/event/events.h +++ b/include/event/events.h @@ -3,7 +3,6 @@ #include #include - namespace extra2d::events { /** @@ -79,6 +78,19 @@ DECLARE_EVENT_0(OnFocus, Engine) */ DECLARE_EVENT_0(OnBlur, Engine) +/** + * @brief 模块配置事件(模板事件,支持任意配置类型) + * + * 使用示例: + * // 发送 AppConfig + * OnModuleConfig::emit(config); + * + * // 发送其他配置 + * struct RenderConfig { int width, height; }; + * OnModuleConfig::emit(renderConfig); + */ +DECLARE_EVENT_T(OnModuleConfig, Engine) + /** * @brief 窗口显示事件 */ diff --git a/include/extra2d.h b/include/extra2d.h index 70deb38..06f60f6 100644 --- a/include/extra2d.h +++ b/include/extra2d.h @@ -40,6 +40,10 @@ #include #include +// Config +#include +#include + // Application #include diff --git a/include/module/module.h b/include/module/module.h new file mode 100644 index 0000000..f7be085 --- /dev/null +++ b/include/module/module.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +namespace extra2d { + +/** + * @brief 模块基类 + * + * 所有引擎模块都继承此类,支持自动生命周期管理 + */ +class Module { +public: + virtual ~Module() = default; + + /** + * @brief 模块初始化 + * @return 初始化是否成功 + */ + virtual bool init() { return true; } + + /** + * @brief 模块更新 + * @param deltaTime 帧间隔时间(秒) + */ + virtual void update(float deltaTime) {} + + /** + * @brief 模块销毁 + */ + virtual void shutdown() {} + + /** + * @brief 获取模块名称 + */ + virtual const char* getName() const = 0; + + /** + * @brief 获取模块优先级(值越小越先初始化) + */ + virtual int32 getPriority() const { return 100; } +}; + +} // namespace extra2d diff --git a/include/module/module_registry.h b/include/module/module_registry.h index 6fda2af..286edc1 100644 --- a/include/module/module_registry.h +++ b/include/module/module_registry.h @@ -1,97 +1,111 @@ #pragma once -#include +#include +#include +#include #include -#include +#include #include namespace extra2d { /** - * @brief 模块注册表 - 非单例 + * @brief 模块工厂函数类型 + */ +using ModuleFactory = std::function()>; + +/** + * @brief 模块注册信息 + */ +struct ModuleInfo { + std::string name; + ModuleFactory factory; + int32 priority; + std::type_index type; +}; + +/** + * @brief 模块注册表 * - * 管理所有模块的生命周期,通过事件总线协调模块间的通信 - * 支持按优先级排序初始化和关闭 + * 支持自动模块注册和获取 + * 使用服务定位器模式 */ class ModuleRegistry { public: - ModuleRegistry(); - ~ModuleRegistry(); - - // 禁止拷贝 - ModuleRegistry(const ModuleRegistry &) = delete; - ModuleRegistry &operator=(const ModuleRegistry &) = delete; - - // 允许移动 - ModuleRegistry(ModuleRegistry &&) noexcept; - ModuleRegistry &operator=(ModuleRegistry &&) noexcept; - /** - * @brief 注册模块 - * @param module 模块实例指针(不由注册表管理生命周期) + * @brief 获取全局注册表实例 */ - void registerModule(IModule *module); + static ModuleRegistry &instance(); /** - * @brief 注销模块 + * @brief 注册模块类型 + * @tparam T 模块类型 * @param name 模块名称 + * @param priority 初始化优先级 */ - void unregisterModule(const char *name); + template + void registerModule(const char *name, int32 priority = 100) { + static_assert(std::is_base_of_v, "T must inherit from Module"); + + ModuleInfo info{ + name, []() -> std::unique_ptr { return std::make_unique(); }, + priority, std::type_index(typeid(T))}; + + registrations_.push_back(std::move(info)); + } /** - * @brief 获取模块 - * @param name 模块名称 - * @return 模块指针,不存在返回 nullptr + * @brief 创建所有已注册的模块 + * @return 创建的模块列表(已按优先级排序) */ - IModule *getModule(const char *name) const; + std::vector> createModules(); /** - * @brief 检查模块是否存在 - * @param name 模块名称 - * @return 是否存在 + * @brief 获取已注册的模块信息列表 */ - bool hasModule(const char *name) const; - - /** - * @brief 获取所有模块 - * @return 模块列表 - */ - std::vector getAllModules() const; - - /** - * @brief 获取指定类型的所有模块 - * @param type 模块类型 - * @return 模块列表 - */ - std::vector getModulesByType(ModuleType type) const; - - /** - * @brief 初始化所有模块(按优先级排序) - * @return 是否全部初始化成功 - */ - bool initAll(); - - /** - * @brief 关闭所有模块(按优先级逆序) - */ - void shutdownAll(); - - /** - * @brief 获取模块数量 - * @return 模块数量 - */ - size_t getModuleCount() const; + const std::vector &getRegistrations() const { + return registrations_; + } private: - /** - * @brief 按优先级排序模块 - */ - void sortModules(); - - std::vector modules_; - std::unordered_map moduleMap_; - bool sorted_ = false; - bool inited_ = false; + ModuleRegistry() = default; + std::vector registrations_; }; +/** + * @brief 自动模块注册辅助类 + */ +template class ModuleRegistrar { +public: + ModuleRegistrar(const char *name, int32 priority = 100) { + ModuleRegistry::instance().registerModule(name, priority); + } +}; + +/** + * @brief 模块注册宏 + * + * 在模块类定义中使用,实现自动注册 + * 示例: + * class MyModule : public Module { + * E2D_REGISTER_MODULE(MyModule, "MyModule", 10) + * public: + * // ... + * }; + */ +#define E2D_REGISTER_MODULE(ClassName, Name, Priority) \ +private: \ + static inline const extra2d::ModuleRegistrar _registrar{ \ + Name, Priority}; \ + \ +public: \ + const char *getName() const override { return Name; } \ + int32 getPriority() const override { return Priority; } + +/** + * @brief 简化版模块注册宏(使用类名作为模块名) + */ +#define E2D_REGISTER_MODULE_SIMPLE(ClassName) \ + E2D_REGISTER_MODULE(ClassName, #ClassName, 100) + } // namespace extra2d diff --git a/include/module/timer_module.h b/include/module/timer_module.h deleted file mode 100644 index 3a5a8bd..0000000 --- a/include/module/timer_module.h +++ /dev/null @@ -1,149 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace extra2d { - -/** - * @brief 定时器句柄 - */ -using TimerId = uint32; -constexpr TimerId INVALID_TIMER_ID = 0; - -/** - * @brief 定时器回调类型 - */ -using TimerCallback = std::function; -using TimerUpdateCallback = std::function; - -/** - * @brief 定时器信息 - */ -struct TimerInfo { - TimerId id = INVALID_TIMER_ID; - float interval = 0.0f; // 间隔时间(秒) - float elapsed = 0.0f; // 已过去的时间 - uint32 repeat = 0; // 重复次数(0表示无限) - uint32 executed = 0; // 已执行次数 - bool paused = false; // 是否暂停 - bool cancelled = false; // 是否取消 - TimerCallback callback; // 回调函数 - TimerUpdateCallback updateCallback; // 带dt的回调 -}; - -/** - * @brief 定时器模块 - * - * 提供定时任务管理功能,作为引擎核心模块运行 - * 通过事件总线接收更新事件,无需直接依赖 - */ -class TimerModule : public IModule { -public: - TimerModule(); - ~TimerModule() override; - - // 禁止拷贝 - TimerModule(const TimerModule&) = delete; - TimerModule& operator=(const TimerModule&) = delete; - - // 允许移动 - TimerModule(TimerModule&&) noexcept; - TimerModule& operator=(TimerModule&&) noexcept; - - // IModule 接口实现 - const char* name() const override { return "Timer"; } - ModuleType type() const override { return ModuleType::Core; } - int priority() const override { return Pri::System; } - bool init() override; - void shutdown() override; - - /** - * @brief 调度一次性任务 - * @param delay 延迟时间(秒) - * @param callback 回调函数 - * @return 定时器ID - */ - TimerId scheduleOnce(float delay, TimerCallback callback); - - /** - * @brief 调度重复任务 - * @param interval 间隔时间(秒) - * @param repeat 重复次数(0表示无限) - * @param callback 回调函数 - * @return 定时器ID - */ - TimerId scheduleRepeat(float interval, uint32 repeat, TimerCallback callback); - - /** - * @brief 调度每帧更新任务 - * @param callback 回调函数(接收dt) - * @return 定时器ID - */ - TimerId scheduleUpdate(TimerUpdateCallback callback); - - /** - * @brief 取消定时器 - * @param id 定时器ID - */ - void cancel(TimerId id); - - /** - * @brief 暂停定时器 - * @param id 定时器ID - */ - void pause(TimerId id); - - /** - * @brief 恢复定时器 - * @param id 定时器ID - */ - void resume(TimerId id); - - /** - * @brief 设置时间缩放 - * @param scale 时间缩放比例(1.0为正常) - */ - void setTimeScale(float scale) { timeScale_ = scale; } - - /** - * @brief 获取时间缩放 - */ - float getTimeScale() const { return timeScale_; } - - /** - * @brief 取消所有定时器 - */ - void cancelAll(); - - /** - * @brief 获取活动定时器数量 - */ - size_t getActiveCount() const; - - /** - * @brief 更新所有定时器(由 Context 调用) - * @param dt 帧间隔时间 - */ - void update(float dt); - -private: - /** - * @brief 生成唯一ID - */ - TimerId generateId(); - - std::unordered_map> timers_; - std::vector pendingRemove_; - TimerId nextId_ = 1; - float timeScale_ = 1.0f; - bool inUpdate_ = false; -}; - -} // namespace extra2d diff --git a/include/platform/file.h b/include/platform/file_module.h similarity index 91% rename from include/platform/file.h rename to include/platform/file_module.h index a9e9935..7417b11 100644 --- a/include/platform/file.h +++ b/include/platform/file_module.h @@ -1,8 +1,8 @@ #pragma once -#include +#include +#include #include -#include #include #include @@ -37,7 +37,10 @@ struct FileData { * 提供跨平台文件系统操作 * 非单例设计,通过 Context 管理生命周期 */ -class FileModule : public IModule { +class FileModule : public Module { + // 自动注册到模块系统,优先级为 20(系统模块) + E2D_REGISTER_MODULE(FileModule, "File", 20) + public: FileModule(); ~FileModule() override; @@ -50,10 +53,7 @@ public: FileModule(FileModule&&) noexcept; FileModule& operator=(FileModule&&) noexcept; - // IModule 接口实现 - const char* name() const override { return "File"; } - ModuleType type() const override { return ModuleType::System; } - int priority() const override { return Pri::File; } + // Module 接口实现 bool init() override; void shutdown() override; diff --git a/include/platform/input.h b/include/platform/input.h deleted file mode 100644 index 55aee26..0000000 --- a/include/platform/input.h +++ /dev/null @@ -1,378 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace extra2d { - -/** - * @brief 键键码别名 - */ -using Key = SDL_Scancode; - -/** - * @brief 按键常量 - */ -namespace Keys { -constexpr Key Unknown = SDL_SCANCODE_UNKNOWN; -constexpr Key A = SDL_SCANCODE_A; -constexpr Key B = SDL_SCANCODE_B; -constexpr Key C = SDL_SCANCODE_C; -constexpr Key D = SDL_SCANCODE_D; -constexpr Key E = SDL_SCANCODE_E; -constexpr Key F = SDL_SCANCODE_F; -constexpr Key G = SDL_SCANCODE_G; -constexpr Key H = SDL_SCANCODE_H; -constexpr Key I = SDL_SCANCODE_I; -constexpr Key J = SDL_SCANCODE_J; -constexpr Key K = SDL_SCANCODE_K; -constexpr Key L = SDL_SCANCODE_L; -constexpr Key M = SDL_SCANCODE_M; -constexpr Key N = SDL_SCANCODE_N; -constexpr Key O = SDL_SCANCODE_O; -constexpr Key P = SDL_SCANCODE_P; -constexpr Key Q = SDL_SCANCODE_Q; -constexpr Key R = SDL_SCANCODE_R; -constexpr Key S = SDL_SCANCODE_S; -constexpr Key T = SDL_SCANCODE_T; -constexpr Key U = SDL_SCANCODE_U; -constexpr Key V = SDL_SCANCODE_V; -constexpr Key W = SDL_SCANCODE_W; -constexpr Key X = SDL_SCANCODE_X; -constexpr Key Y = SDL_SCANCODE_Y; -constexpr Key Z = SDL_SCANCODE_Z; -constexpr Key Num0 = SDL_SCANCODE_0; -constexpr Key Num1 = SDL_SCANCODE_1; -constexpr Key Num2 = SDL_SCANCODE_2; -constexpr Key Num3 = SDL_SCANCODE_3; -constexpr Key Num4 = SDL_SCANCODE_4; -constexpr Key Num5 = SDL_SCANCODE_5; -constexpr Key Num6 = SDL_SCANCODE_6; -constexpr Key Num7 = SDL_SCANCODE_7; -constexpr Key Num8 = SDL_SCANCODE_8; -constexpr Key Num9 = SDL_SCANCODE_9; -constexpr Key F1 = SDL_SCANCODE_F1; -constexpr Key F2 = SDL_SCANCODE_F2; -constexpr Key F3 = SDL_SCANCODE_F3; -constexpr Key F4 = SDL_SCANCODE_F4; -constexpr Key F5 = SDL_SCANCODE_F5; -constexpr Key F6 = SDL_SCANCODE_F6; -constexpr Key F7 = SDL_SCANCODE_F7; -constexpr Key F8 = SDL_SCANCODE_F8; -constexpr Key F9 = SDL_SCANCODE_F9; -constexpr Key F10 = SDL_SCANCODE_F10; -constexpr Key F11 = SDL_SCANCODE_F11; -constexpr Key F12 = SDL_SCANCODE_F12; -constexpr Key Space = SDL_SCANCODE_SPACE; -constexpr Key Enter = SDL_SCANCODE_RETURN; -constexpr Key Escape = SDL_SCANCODE_ESCAPE; -constexpr Key Tab = SDL_SCANCODE_TAB; -constexpr Key Backspace = SDL_SCANCODE_BACKSPACE; -constexpr Key Insert = SDL_SCANCODE_INSERT; -constexpr Key Delete = SDL_SCANCODE_DELETE; -constexpr Key Home = SDL_SCANCODE_HOME; -constexpr Key End = SDL_SCANCODE_END; -constexpr Key PageUp = SDL_SCANCODE_PAGEUP; -constexpr Key PageDown = SDL_SCANCODE_PAGEDOWN; -constexpr Key Left = SDL_SCANCODE_LEFT; -constexpr Key Right = SDL_SCANCODE_RIGHT; -constexpr Key Up = SDL_SCANCODE_UP; -constexpr Key Down = SDL_SCANCODE_DOWN; -constexpr Key LeftShift = SDL_SCANCODE_LSHIFT; -constexpr Key RightShift = SDL_SCANCODE_RSHIFT; -constexpr Key LeftCtrl = SDL_SCANCODE_LCTRL; -constexpr Key RightCtrl = SDL_SCANCODE_RCTRL; -constexpr Key LeftAlt = SDL_SCANCODE_LALT; -constexpr Key RightAlt = SDL_SCANCODE_RALT; -} // namespace Keys - -/** - * @brief 鼠标按键 - */ -enum class MouseBtn : uint8 { - Left = 0, - Middle = 1, - Right = 2, - X1 = 3, - X2 = 4, - Count = 5 -}; - -/** - * @brief 游戏手柄按键 - */ -enum class GamepadBtn : uint8 { - A = 0, - B = 1, - X = 2, - Y = 3, - Back = 4, - Guide = 5, - Start = 6, - LeftStick = 7, - RightStick = 8, - LeftShoulder = 9, - RightShoulder = 10, - DPadUp = 11, - DPadDown = 12, - DPadLeft = 13, - DPadRight = 14, - Count = 15 -}; - -/** - * @brief 游戏手柄轴 - */ -enum class GamepadAxis : uint8 { - LeftX = 0, - LeftY = 1, - RightX = 2, - RightY = 3, - TriggerLeft = 4, - TriggerRight = 5, - Count = 6 -}; - -/** - * @brief 触摸状态 - */ -enum class TouchState : uint8 { None = 0, Began, Moved, Ended, Cancelled }; - -/** - * @brief 触摸点信息 - */ -struct TouchPoint { - int64 id = 0; - float x = 0.0f; - float y = 0.0f; - float prevX = 0.0f; - float prevY = 0.0f; - float deltaX = 0.0f; - float deltaY = 0.0f; - TouchState state = TouchState::None; - float pressure = 1.0f; -}; - -/** - * @brief 按键回调类型 - */ -using KeyCb = std::function; -using MouseBtnCb = std::function; -using TouchCb = std::function; - -/** - * @brief 输入模块 - * - * 管理键盘、鼠标、触摸、游戏手柄输入 - * 非单例设计,通过 Context 管理生命周期 - */ -class InputModule : public IModule { -public: - InputModule(); - ~InputModule() override; - - // 禁止拷贝 - InputModule(const InputModule&) = delete; - InputModule& operator=(const InputModule&) = delete; - - // 允许移动 - InputModule(InputModule&&) noexcept; - InputModule& operator=(InputModule&&) noexcept; - - // IModule 接口实现 - const char* name() const override { return "Input"; } - ModuleType type() const override { return ModuleType::System; } - int priority() const override { return Pri::Input; } - bool init() override; - void shutdown() override; - - /** - * @brief 处理输入事件 - */ - void processEvent(const SDL_Event& evt); - - /** - * @brief 更新输入状态(每帧调用) - */ - void update(); - - // ========== 键盘 ========== - - /** - * @brief 检查按键是否按下 - */ - bool isKeyDown(Key key) const; - - /** - * @brief 检查按键是否刚按下 - */ - bool isKeyPressed(Key key) const; - - /** - * @brief 检查按键是否刚释放 - */ - bool isKeyReleased(Key key) const; - - // ========== 鼠标 ========== - - /** - * @brief 获取鼠标位置 - */ - void getMousePos(int32& x, int32& y) const; - - /** - * @brief 获取鼠标位置(浮点) - */ - void getMousePos(float& x, float& y) const; - - /** - * @brief 检查鼠标按键是否按下 - */ - bool isMouseBtnDown(MouseBtn btn) const; - - /** - * @brief 检查鼠标按键是否刚按下 - */ - bool isMouseBtnPressed(MouseBtn btn) const; - - /** - * @brief 检查鼠标按键是否刚释放 - */ - bool isMouseBtnReleased(MouseBtn btn) const; - - /** - * @brief 获取鼠标滚轮 - */ - int32 getMouseWheel() const; - - // ========== 触摸 ========== - - /** - * @brief 获取触摸点数量 - */ - int32 touchCount() const; - - /** - * @brief 获取触摸点 - * @param idx 触摸点索引 - * @return 触摸点信息,无效索引返回 nullptr - */ - const TouchPoint* getTouch(int32 idx) const; - - /** - * @brief 根据 ID 获取触摸点 - */ - const TouchPoint* getTouchById(int64 id) const; - - /** - * @brief 检查是否有触摸 - */ - bool hasTouch() const { return touchCount() > 0; } - - /** - * @brief 获取所有活跃触摸点 - */ - const std::vector& getTouches() const { return activeTouches_; } - - // ========== 游戏手柄 ========== - - /** - * @brief 连接的游戏手柄数量 - */ - int32 gamepadCount() const; - - /** - * @brief 检查手柄按键是否按下 - */ - bool isGamepadBtnDown(int32 idx, GamepadBtn btn) const; - - /** - * @brief 检查手柄按键是否刚按下 - */ - bool isGamepadBtnPressed(int32 idx, GamepadBtn btn) const; - - /** - * @brief 获取手柄轴值 (-1.0 到 1.0) - */ - float getGamepadAxis(int32 idx, GamepadAxis axis) const; - - // ========== 回调设置 ========== - - /** - * @brief 设置按键按下回调 - */ - void setOnKeyDown(KeyCb cb) { onKeyDown_ = std::move(cb); } - - /** - * @brief 设置按键释放回调 - */ - void setOnKeyUp(KeyCb cb) { onKeyUp_ = std::move(cb); } - - /** - * @brief 设置鼠标按下回调 - */ - void setOnMouseDown(MouseBtnCb cb) { onMouseDown_ = std::move(cb); } - - /** - * @brief 设置鼠标释放回调 - */ - void setOnMouseUp(MouseBtnCb cb) { onMouseUp_ = std::move(cb); } - - /** - * @brief 设置触摸开始回调 - */ - void setOnTouchBegan(TouchCb cb) { onTouchBegan_ = std::move(cb); } - - /** - * @brief 设置触摸移动回调 - */ - void setOnTouchMoved(TouchCb cb) { onTouchMoved_ = std::move(cb); } - - /** - * @brief 设置触摸结束回调 - */ - void setOnTouchEnded(TouchCb cb) { onTouchEnded_ = std::move(cb); } - -private: - static constexpr int32 KEY_COUNT = SDL_NUM_SCANCODES; - static constexpr int32 MAX_GAMEPADS = 4; - static constexpr int32 MAX_TOUCHES = 10; - - std::array keyState_{}; - std::array keyPrev_{}; - - int32 mouseX_ = 0; - int32 mouseY_ = 0; - int32 mouseWheel_ = 0; - std::array(MouseBtn::Count)> mouseState_{}; - std::array(MouseBtn::Count)> mousePrev_{}; - - std::vector activeTouches_; - std::vector endedTouches_; - - SDL_GameController* gamepads_[MAX_GAMEPADS] = {}; - std::array(GamepadBtn::Count)> padState_[MAX_GAMEPADS]; - std::array(GamepadBtn::Count)> padPrev_[MAX_GAMEPADS]; - - KeyCb onKeyDown_; - KeyCb onKeyUp_; - MouseBtnCb onMouseDown_; - MouseBtnCb onMouseUp_; - TouchCb onTouchBegan_; - TouchCb onTouchMoved_; - TouchCb onTouchEnded_; - - void openGamepad(int32 idx); - void closeGamepad(int32 idx); - - void processTouchDown(const SDL_TouchFingerEvent& evt); - void processTouchUp(const SDL_TouchFingerEvent& evt); - void processTouchMotion(const SDL_TouchFingerEvent& evt); -}; - -} // namespace extra2d diff --git a/include/platform/input_module.h b/include/platform/input_module.h new file mode 100644 index 0000000..439eadf --- /dev/null +++ b/include/platform/input_module.h @@ -0,0 +1,376 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 键码别名 + */ +using Key = SDL_Scancode; + +/** + * @brief 按键常量 + */ +namespace Keys { +constexpr Key Unknown = SDL_SCANCODE_UNKNOWN; +constexpr Key A = SDL_SCANCODE_A; +constexpr Key B = SDL_SCANCODE_B; +constexpr Key C = SDL_SCANCODE_C; +constexpr Key D = SDL_SCANCODE_D; +constexpr Key E = SDL_SCANCODE_E; +constexpr Key F = SDL_SCANCODE_F; +constexpr Key G = SDL_SCANCODE_G; +constexpr Key H = SDL_SCANCODE_H; +constexpr Key I = SDL_SCANCODE_I; +constexpr Key J = SDL_SCANCODE_J; +constexpr Key K = SDL_SCANCODE_K; +constexpr Key L = SDL_SCANCODE_L; +constexpr Key M = SDL_SCANCODE_M; +constexpr Key N = SDL_SCANCODE_N; +constexpr Key O = SDL_SCANCODE_O; +constexpr Key P = SDL_SCANCODE_P; +constexpr Key Q = SDL_SCANCODE_Q; +constexpr Key R = SDL_SCANCODE_R; +constexpr Key S = SDL_SCANCODE_S; +constexpr Key T = SDL_SCANCODE_T; +constexpr Key U = SDL_SCANCODE_U; +constexpr Key V = SDL_SCANCODE_V; +constexpr Key W = SDL_SCANCODE_W; +constexpr Key X = SDL_SCANCODE_X; +constexpr Key Y = SDL_SCANCODE_Y; +constexpr Key Z = SDL_SCANCODE_Z; +constexpr Key Num0 = SDL_SCANCODE_0; +constexpr Key Num1 = SDL_SCANCODE_1; +constexpr Key Num2 = SDL_SCANCODE_2; +constexpr Key Num3 = SDL_SCANCODE_3; +constexpr Key Num4 = SDL_SCANCODE_4; +constexpr Key Num5 = SDL_SCANCODE_5; +constexpr Key Num6 = SDL_SCANCODE_6; +constexpr Key Num7 = SDL_SCANCODE_7; +constexpr Key Num8 = SDL_SCANCODE_8; +constexpr Key Num9 = SDL_SCANCODE_9; +constexpr Key F1 = SDL_SCANCODE_F1; +constexpr Key F2 = SDL_SCANCODE_F2; +constexpr Key F3 = SDL_SCANCODE_F3; +constexpr Key F4 = SDL_SCANCODE_F4; +constexpr Key F5 = SDL_SCANCODE_F5; +constexpr Key F6 = SDL_SCANCODE_F6; +constexpr Key F7 = SDL_SCANCODE_F7; +constexpr Key F8 = SDL_SCANCODE_F8; +constexpr Key F9 = SDL_SCANCODE_F9; +constexpr Key F10 = SDL_SCANCODE_F10; +constexpr Key F11 = SDL_SCANCODE_F11; +constexpr Key F12 = SDL_SCANCODE_F12; +constexpr Key Space = SDL_SCANCODE_SPACE; +constexpr Key Enter = SDL_SCANCODE_RETURN; +constexpr Key Escape = SDL_SCANCODE_ESCAPE; +constexpr Key Tab = SDL_SCANCODE_TAB; +constexpr Key Backspace = SDL_SCANCODE_BACKSPACE; +constexpr Key Insert = SDL_SCANCODE_INSERT; +constexpr Key Delete = SDL_SCANCODE_DELETE; +constexpr Key Home = SDL_SCANCODE_HOME; +constexpr Key End = SDL_SCANCODE_END; +constexpr Key PageUp = SDL_SCANCODE_PAGEUP; +constexpr Key PageDown = SDL_SCANCODE_PAGEDOWN; +constexpr Key Left = SDL_SCANCODE_LEFT; +constexpr Key Right = SDL_SCANCODE_RIGHT; +constexpr Key Up = SDL_SCANCODE_UP; +constexpr Key Down = SDL_SCANCODE_DOWN; +constexpr Key LeftShift = SDL_SCANCODE_LSHIFT; +constexpr Key RightShift = SDL_SCANCODE_RSHIFT; +constexpr Key LeftCtrl = SDL_SCANCODE_LCTRL; +constexpr Key RightCtrl = SDL_SCANCODE_RCTRL; +constexpr Key LeftAlt = SDL_SCANCODE_LALT; +constexpr Key RightAlt = SDL_SCANCODE_RALT; +} // namespace Keys + +/** + * @brief 鼠标按键 + */ +enum class MouseBtn : uint8 { + Left = 0, + Middle = 1, + Right = 2, + X1 = 3, + X2 = 4, + Count = 5 +}; + +/** + * @brief 游戏手柄按键 + */ +enum class GamepadBtn : uint8 { + A = 0, + B = 1, + X = 2, + Y = 3, + Back = 4, + Guide = 5, + Start = 6, + LeftStick = 7, + RightStick = 8, + LeftShoulder = 9, + RightShoulder = 10, + DPadUp = 11, + DPadDown = 12, + DPadLeft = 13, + DPadRight = 14, + Count = 15 +}; + +/** + * @brief 游戏手柄轴 + */ +enum class GamepadAxis : uint8 { + LeftX = 0, + LeftY = 1, + RightX = 2, + RightY = 3, + TriggerLeft = 4, + TriggerRight = 5, + Count = 6 +}; + +/** + * @brief 触摸状态 + */ +enum class TouchState : uint8 { None = 0, Began, Moved, Ended, Cancelled }; + +/** + * @brief 触摸点信息 + */ +struct TouchPoint { + int64 id = 0; + float x = 0.0f; + float y = 0.0f; + float prevX = 0.0f; + float prevY = 0.0f; + float deltaX = 0.0f; + float deltaY = 0.0f; + TouchState state = TouchState::None; + float pressure = 1.0f; +}; + +/** + * @brief 按键回调类型 + */ +using KeyCb = std::function; +using MouseBtnCb = std::function; +using TouchCb = std::function; + +/** + * @brief 输入模块 - 简化版 + * + * 管理键盘、鼠标、触摸、游戏手柄输入 + * 使用新的 Module 基类,支持自动注册 + */ +class InputModule : public Module { + // 自动注册到模块系统,优先级为 10 + E2D_REGISTER_MODULE(InputModule, "Input", 10) + +public: + InputModule(); + ~InputModule() override; + + // 禁止拷贝 + InputModule(const InputModule &) = delete; + InputModule &operator=(const InputModule &) = delete; + + // 允许移动 + InputModule(InputModule &&) noexcept; + InputModule &operator=(InputModule &&) noexcept; + + // Module 接口实现 + bool init() override; + void shutdown() override; + void update(float deltaTime) override; + + /** + * @brief 处理输入事件 + */ + void processEvent(const SDL_Event &evt); + + // ========== 键盘 ========== + + /** + * @brief 检查按键是否按下 + */ + bool isKeyDown(Key key) const; + + /** + * @brief 检查按键是否刚按下 + */ + bool isKeyPressed(Key key) const; + + /** + * @brief 检查按键是否刚释放 + */ + bool isKeyReleased(Key key) const; + + // ========== 鼠标 ========== + + /** + * @brief 获取鼠标位置 + */ + void getMousePos(int32 &x, int32 &y) const; + + /** + * @brief 获取鼠标位置(浮点) + */ + void getMousePos(float &x, float &y) const; + + /** + * @brief 检查鼠标按键是否按下 + */ + bool isMouseBtnDown(MouseBtn btn) const; + + /** + * @brief 检查鼠标按键是否刚按下 + */ + bool isMouseBtnPressed(MouseBtn btn) const; + + /** + * @brief 检查鼠标按键是否刚释放 + */ + bool isMouseBtnReleased(MouseBtn btn) const; + + /** + * @brief 获取鼠标滚轮 + */ + int32 getMouseWheel() const; + + // ========== 触摸 ========== + + /** + * @brief 获取触摸点数量 + */ + int32 touchCount() const; + + /** + * @brief 获取触摸点 + * @param idx 触摸点索引 + * @return 触摸点信息,无效索引返回 nullptr + */ + const TouchPoint *getTouch(int32 idx) const; + + /** + * @brief 根据 ID 获取触摸点 + */ + const TouchPoint *getTouchById(int64 id) const; + + /** + * @brief 检查是否有触摸 + */ + bool hasTouch() const { return touchCount() > 0; } + + /** + * @brief 获取所有活跃触摸点 + */ + const std::vector &getTouches() const { return activeTouches_; } + + // ========== 游戏手柄 ========== + + /** + * @brief 连接的游戏手柄数量 + */ + int32 gamepadCount() const; + + /** + * @brief 检查手柄按键是否按下 + */ + bool isGamepadBtnDown(int32 idx, GamepadBtn btn) const; + + /** + * @brief 检查手柄按键是否刚按下 + */ + bool isGamepadBtnPressed(int32 idx, GamepadBtn btn) const; + + /** + * @brief 获取手柄轴值 (-1.0 到 1.0) + */ + float getGamepadAxis(int32 idx, GamepadAxis axis) const; + + // ========== 回调设置 ========== + + /** + * @brief 设置按键按下回调 + */ + void setOnKeyDown(KeyCb cb) { onKeyDown_ = std::move(cb); } + + /** + * @brief 设置按键释放回调 + */ + void setOnKeyUp(KeyCb cb) { onKeyUp_ = std::move(cb); } + + /** + * @brief 设置鼠标按下回调 + */ + void setOnMouseDown(MouseBtnCb cb) { onMouseDown_ = std::move(cb); } + + /** + * @brief 设置鼠标释放回调 + */ + void setOnMouseUp(MouseBtnCb cb) { onMouseUp_ = std::move(cb); } + + /** + * @brief 设置触摸开始回调 + */ + void setOnTouchBegan(TouchCb cb) { onTouchBegan_ = std::move(cb); } + + /** + * @brief 设置触摸移动回调 + */ + void setOnTouchMoved(TouchCb cb) { onTouchMoved_ = std::move(cb); } + + /** + * @brief 设置触摸结束回调 + */ + void setOnTouchEnded(TouchCb cb) { onTouchEnded_ = std::move(cb); } + +private: + static constexpr int32 KEY_COUNT = SDL_NUM_SCANCODES; + static constexpr int32 MAX_GAMEPADS = 4; + static constexpr int32 MAX_TOUCHES = 10; + + std::array keyState_{}; + std::array keyPrev_{}; + + int32 mouseX_ = 0; + int32 mouseY_ = 0; + int32 mouseWheel_ = 0; + std::array(MouseBtn::Count)> mouseState_{}; + std::array(MouseBtn::Count)> mousePrev_{}; + + std::vector activeTouches_; + std::vector endedTouches_; + + SDL_GameController *gamepads_[MAX_GAMEPADS] = {}; + std::array(GamepadBtn::Count)> + padState_[MAX_GAMEPADS]; + std::array(GamepadBtn::Count)> + padPrev_[MAX_GAMEPADS]; + + KeyCb onKeyDown_; + KeyCb onKeyUp_; + MouseBtnCb onMouseDown_; + MouseBtnCb onMouseUp_; + TouchCb onTouchBegan_; + TouchCb onTouchMoved_; + TouchCb onTouchEnded_; + + void openGamepad(int32 idx); + void closeGamepad(int32 idx); + + void processTouchDown(const SDL_TouchFingerEvent &evt); + void processTouchUp(const SDL_TouchFingerEvent &evt); + void processTouchMotion(const SDL_TouchFingerEvent &evt); +}; + +} // namespace extra2d diff --git a/include/platform/sdl2.h b/include/platform/sdl2.h deleted file mode 100644 index 4d18a54..0000000 --- a/include/platform/sdl2.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include -#include - -namespace extra2d { - -/** - * @brief SDL2 初始化和生命周期管理 - * - * 提供 SDL2 子系统的统一初始化和清理接口 - */ -class Sdl2 { -public: - /** - * @brief 初始化 SDL2 核心子系统 - * @return true 初始化成功 - */ - static bool initCore(); - - /** - * @brief 初始化视频子系统 - * @return true 初始化成功 - */ - static bool initVideo(); - - /** - * @brief 初始化音频子系统 - * @return true 初始化成功 - */ - static bool initAudio(); - - /** - * @brief 初始化游戏控制器子系统 - * @return true 初始化成功 - */ - static bool initGamepad(); - - /** - * @brief 初始化所有子系统 - * @return true 初始化成功 - */ - static bool initAll(); - - /** - * @brief 关闭 SDL2 - */ - static void shutdown(); - - /** - * @brief 检查是否已初始化 - */ - static bool isInited() { return inited_; } - -private: - static bool inited_; -}; - -} // namespace extra2d diff --git a/include/platform/window.h b/include/platform/window.h deleted file mode 100644 index 005ab6b..0000000 --- a/include/platform/window.h +++ /dev/null @@ -1,168 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace extra2d { - -/** - * @brief 窗口配置 - */ -struct WindowCfg { - std::string title = "Extra2D"; - int32 width = 1280; - int32 height = 720; - bool fullscreen = false; - bool resizable = true; - bool vsync = true; - int32 glMajor = 3; - int32 glMinor = 3; -}; - -/** - * @brief 窗口事件回调 - */ -using ResizeCb = std::function; -using CloseCb = std::function; - -/** - * @brief 窗口模块 - * - * 管理 SDL2 窗口和 OpenGL 上下文 - * 非单例设计,通过 Context 管理生命周期 - */ -class WindowModule : public IModule { -public: - WindowModule(); - ~WindowModule() override; - - // 禁止拷贝 - WindowModule(const WindowModule&) = delete; - WindowModule& operator=(const WindowModule&) = delete; - - // 允许移动 - WindowModule(WindowModule&&) noexcept; - WindowModule& operator=(WindowModule&&) noexcept; - - // IModule 接口实现 - const char* name() const override { return "Window"; } - ModuleType type() const override { return ModuleType::System; } - int priority() const override { return Pri::Window; } - bool init() override; - void shutdown() override; - - /** - * @brief 使用配置创建窗口 - */ - bool create(const WindowCfg& cfg); - - /** - * @brief 处理窗口事件 - * @return true 继续运行,false 应退出 - */ - bool pollEvents(); - - /** - * @brief 交换缓冲区 - */ - void swapBuffers(); - - /** - * @brief 获取 SDL 窗口句柄 - */ - SDL_Window* handle() const { return window_; } - - /** - * @brief 获取 OpenGL 上下文 - */ - SDL_GLContext glContext() const { return glCtx_; } - - /** - * @brief 获取窗口尺寸 - */ - Size getSize() const; - - /** - * @brief 获取窗口位置 - */ - Vec2 getPosition() const; - - /** - * @brief 设置窗口尺寸 - */ - void setSize(int32 w, int32 h); - - /** - * @brief 设置窗口标题 - */ - void setTitle(const std::string& title); - - /** - * @brief 设置全屏模式 - */ - void setFullscreen(bool fullscreen); - - /** - * @brief 检查是否全屏 - */ - bool isFullscreen() const; - - /** - * @brief 设置垂直同步 - */ - void setVsync(bool vsync); - - /** - * @brief 检查是否垂直同步 - */ - bool isVsync() const; - - /** - * @brief 显示/隐藏窗口 - */ - void setVisible(bool visible); - - /** - * @brief 检查窗口是否可见 - */ - bool isVisible() const; - - /** - * @brief 设置窗口关闭回调 - */ - void setOnClose(CloseCb cb) { onClose_ = std::move(cb); } - - /** - * @brief 设置窗口大小改变回调 - */ - void setOnResize(ResizeCb cb) { onResize_ = std::move(cb); } - - /** - * @brief 请求关闭窗口 - */ - void requestClose() { shouldClose_ = true; } - - /** - * @brief 检查是否应该关闭 - */ - bool shouldClose() const { return shouldClose_; } - -private: - void handleWindowEvent(const SDL_WindowEvent& evt); - - SDL_Window* window_ = nullptr; - SDL_GLContext glCtx_ = nullptr; - bool shouldClose_ = false; - bool vsync_ = true; - - CloseCb onClose_; - ResizeCb onResize_; -}; - -} // namespace extra2d diff --git a/include/platform/window_module.h b/include/platform/window_module.h new file mode 100644 index 0000000..b1c40a5 --- /dev/null +++ b/include/platform/window_module.h @@ -0,0 +1,181 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 窗口事件回调 + */ +using ResizeCb = std::function; +using CloseCb = std::function; + +/** + * @brief 窗口模块 - 简化版 + * + * 管理 SDL2 窗口和 OpenGL 上下文 + * 使用新的 Module 基类,支持自动注册 + */ +class WindowModule : public Module { + // 自动注册到模块系统,优先级为 0(最先初始化) + E2D_REGISTER_MODULE(WindowModule, "Window", 0) + +public: + WindowModule(); + ~WindowModule() override; + + // 禁止拷贝 + WindowModule(const WindowModule &) = delete; + WindowModule &operator=(const WindowModule &) = delete; + + // 允许移动 + WindowModule(WindowModule &&) noexcept; + WindowModule &operator=(WindowModule &&) noexcept; + + // Module 接口实现 + bool init() override; + void shutdown() override; + + /** + * @brief 使用配置创建窗口 + */ + bool create(const WindowCfg &cfg); + + /** + * @brief 处理窗口事件 + * @return true 继续运行,false 应退出 + */ + bool pollEvents(); + + /** + * @brief 交换缓冲区 + */ + void swapBuffers(); + + /** + * @brief 获取 SDL 窗口句柄 + */ + SDL_Window *handle() const { return window_; } + + /** + * @brief 获取 OpenGL 上下文 + */ + SDL_GLContext glContext() const { return glCtx_; } + + /** + * @brief 获取窗口尺寸 + */ + Size getSize() const; + + /** + * @brief 获取窗口位置 + */ + Vec2 getPosition() const; + + /** + * @brief 设置窗口尺寸 + */ + void setSize(int32 w, int32 h); + + /** + * @brief 设置窗口标题 + */ + void setTitle(const std::string &title); + + /** + * @brief 设置全屏模式 + */ + void setFullscreen(bool fullscreen); + + /** + * @brief 检查是否全屏 + */ + bool isFullscreen() const; + + /** + * @brief 设置垂直同步 + */ + void setVsync(bool vsync); + + /** + * @brief 检查是否垂直同步 + */ + bool isVsync() const; + + /** + * @brief 显示/隐藏窗口 + */ + void setVisible(bool visible); + + /** + * @brief 检查窗口是否可见 + */ + bool isVisible() const; + + /** + * @brief 设置窗口关闭回调 + */ + void setOnClose(CloseCb cb) { onClose_ = std::move(cb); } + + /** + * @brief 设置窗口大小改变回调 + */ + void setOnResize(ResizeCb cb) { onResize_ = std::move(cb); } + + /** + * @brief 请求关闭窗口 + */ + void requestClose() { shouldClose_ = true; } + + /** + * @brief 检查是否应该关闭 + */ + bool shouldClose() const { return shouldClose_; } + +private: + void handleWindowEvent(const SDL_WindowEvent &evt); + + /** + * @brief 监听模块配置事件 + * @tparam ConfigT 配置类型 + * @param config 配置对象 + * + * 只处理 AppConfig 类型的配置 + */ + template void onModuleConfig(const ConfigT &config) { + // 只处理 AppConfig 类型 + if constexpr (std::is_same_v) { + WindowCfg cfg; + cfg.title = config.title; + cfg.width = config.width; + cfg.height = config.height; + cfg.fullscreen = config.fullscreen; + cfg.resizable = config.resizable; + cfg.vsync = config.vsync; + cfg.glMajor = config.glMajor; + cfg.glMinor = config.glMinor; + + if (create(cfg)) { + setVisible(true); + } + } + } + + SDL_Window *window_ = nullptr; + SDL_GLContext glCtx_ = nullptr; + bool shouldClose_ = false; + bool vsync_ = true; + + CloseCb onClose_; + ResizeCb onResize_; +}; + +} // namespace extra2d diff --git a/include/utils/timer_module.h b/include/utils/timer_module.h new file mode 100644 index 0000000..4b7c657 --- /dev/null +++ b/include/utils/timer_module.h @@ -0,0 +1,143 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 定时器句柄 + */ +using TimerId = uint32; +constexpr TimerId INVALID_TIMER_ID = 0; + +/** + * @brief 定时器回调类型 + */ +using TimerCallback = std::function; +using TimerUpdateCallback = std::function; + +/** + * @brief 定时器信息 + */ +struct TimerInfo { + TimerId id = INVALID_TIMER_ID; + float interval = 0.0f; // 间隔时间(秒) + float elapsed = 0.0f; // 已过去的时间 + uint32 repeat = 0; // 重复次数(0表示无限) + uint32 executed = 0; // 已执行次数 + bool paused = false; // 是否暂停 + bool cancelled = false; // 是否取消 + TimerCallback callback; // 回调函数 + TimerUpdateCallback updateCallback; // 带dt的回调 +}; + +/** + * @brief 定时器模块 + * + * 提供定时任务管理功能,作为引擎核心模块运行 + * 通过事件总线接收更新事件,无需直接依赖 + */ +class TimerModule : public Module { + // 自动注册到模块系统,优先级为 5(核心模块) + E2D_REGISTER_MODULE(TimerModule, "Timer", 5) + +public: + TimerModule(); + ~TimerModule() override; + + // 禁止拷贝 + TimerModule(const TimerModule &) = delete; + TimerModule &operator=(const TimerModule &) = delete; + + // 允许移动 + TimerModule(TimerModule &&) noexcept; + TimerModule &operator=(TimerModule &&) noexcept; + + // Module 接口实现 + bool init() override; + void shutdown() override; + void update(float dt) override; + + /** + * @brief 调度一次性任务 + * @param delay 延迟时间(秒) + * @param callback 回调函数 + * @return 定时器ID + */ + TimerId scheduleOnce(float delay, TimerCallback callback); + + /** + * @brief 调度重复任务 + * @param interval 间隔时间(秒) + * @param repeat 重复次数(0表示无限) + * @param callback 回调函数 + * @return 定时器ID + */ + TimerId scheduleRepeat(float interval, uint32 repeat, TimerCallback callback); + + /** + * @brief 调度每帧更新任务 + * @param callback 回调函数(接收dt) + * @return 定时器ID + */ + TimerId scheduleUpdate(TimerUpdateCallback callback); + + /** + * @brief 取消定时器 + * @param id 定时器ID + */ + void cancel(TimerId id); + + /** + * @brief 暂停定时器 + * @param id 定时器ID + */ + void pause(TimerId id); + + /** + * @brief 恢复定时器 + * @param id 定时器ID + */ + void resume(TimerId id); + + /** + * @brief 设置时间缩放 + * @param scale 时间缩放比例(1.0为正常) + */ + void setTimeScale(float scale) { timeScale_ = scale; } + + /** + * @brief 获取时间缩放 + */ + float getTimeScale() const { return timeScale_; } + + /** + * @brief 取消所有定时器 + */ + void cancelAll(); + + /** + * @brief 获取活动定时器数量 + */ + size_t getActiveCount() const; + +private: + /** + * @brief 生成唯一ID + */ + TimerId generateId(); + + std::unordered_map> timers_; + std::vector pendingRemove_; + TimerId nextId_ = 1; + float timeScale_ = 1.0f; + bool inUpdate_ = false; +}; + +} // namespace extra2d diff --git a/src/app/application.cpp b/src/app/application.cpp index 5543ce5..459e5a5 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -1,49 +1,25 @@ #include #include #include -#include -#include -#include +#include +#include +#include #include -#include -#include - -#ifdef __SWITCH__ -#include -#endif +#include namespace extra2d { -/** - * @brief 获取当前时间(秒) - */ -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; -#else - using namespace std::chrono; - auto now = steady_clock::now(); - auto duration = now.time_since_epoch(); - return duration_cast>(duration).count(); -#endif -} - std::unique_ptr Application::create() { return std::unique_ptr(new Application()); } Application::Application() = default; -Application::~Application() { - shutdown(); -} +Application::~Application() { shutdown(); } -Application::Application(Application&&) noexcept = default; -Application& Application::operator=(Application&&) noexcept = default; +Application::Application(Application &&) noexcept = default; +Application &Application::operator=(Application &&) noexcept = default; bool Application::init(const AppConfig &config) { if (initialized_) { @@ -53,26 +29,6 @@ bool Application::init(const AppConfig &config) { config_ = config; -#ifdef __SWITCH__ - Result rc; - rc = romfsInit(); - if (R_SUCCEEDED(rc)) { - E2D_LOG_INFO("RomFS initialized successfully"); - } else { - E2D_LOG_WARN("romfsInit failed: {:#08X}", rc); - } - - rc = socketInitializeDefault(); - if (R_FAILED(rc)) { - E2D_LOG_WARN("socketInitializeDefault failed"); - } -#endif - - if (!Sdl2::initAll()) { - E2D_LOG_ERROR("Failed to initialize SDL2"); - return false; - } - // 创建引擎上下文 context_ = Context::create(); if (!context_) { @@ -86,26 +42,11 @@ bool Application::init(const AppConfig &config) { return false; } - // 创建窗口模块 - windowModule_ = std::make_unique(); - WindowCfg wcfg; - wcfg.title = config_.title; - wcfg.width = config_.width; - wcfg.height = config_.height; - wcfg.fullscreen = config_.fullscreen; - wcfg.resizable = config_.resizable; - wcfg.vsync = config_.vsync; - wcfg.glMajor = config_.glMajor; - wcfg.glMinor = config_.glMinor; + // 自动创建所有已注册的模块 + initModules(); - if (!windowModule_->create(wcfg)) { - E2D_LOG_ERROR("Failed to create window"); - return false; - } - windowModule_->setVisible(true); - - // 创建输入模块 - inputModule_ = std::make_unique(); + // 通过事件总线发送配置给所有监听模块 + events::OnModuleConfig::emit(config); initialized_ = true; running_ = true; @@ -113,11 +54,26 @@ bool Application::init(const AppConfig &config) { events::OnInit::emit(); E2D_LOG_INFO("Application initialized successfully"); - E2D_LOG_INFO("Window: {}x{}, Fullscreen: {}, VSync: {}", config_.width, - config_.height, config_.fullscreen, config_.vsync); + E2D_LOG_INFO("Window: {}x{}, Fullscreen: {}, VSync: {}", config.width, + config.height, config.fullscreen, config.vsync); return true; } +void Application::initModules() { + // 从注册表自动创建所有模块 + modules_ = ModuleRegistry::instance().createModules(); + + // 初始化所有模块 + for (auto &module : modules_) { + if (!module->init()) { + E2D_LOG_ERROR("Failed to initialize module: {}", module->getName()); + } else { + E2D_LOG_INFO("Module initialized: {} (priority: {})", module->getName(), + module->getPriority()); + } + } +} + void Application::shutdown() { if (!initialized_) return; @@ -126,20 +82,15 @@ void Application::shutdown() { E2D_LOG_INFO("Shutting down application..."); - // 智能指针自动销毁窗口和输入模块 - inputModule_.reset(); - windowModule_.reset(); + // 按相反顺序销毁模块 + for (auto it = modules_.rbegin(); it != modules_.rend(); ++it) { + (*it)->shutdown(); + } + modules_.clear(); // 关闭上下文 context_.reset(); - Sdl2::shutdown(); - -#ifdef __SWITCH__ - romfsExit(); - socketExit(); -#endif - initialized_ = false; running_ = false; @@ -152,10 +103,50 @@ void Application::run() { return; } - lastFrameTime_ = getTimeSeconds(); + // 使用 SDL 的高精度计时器 + Uint64 perfFreq = SDL_GetPerformanceFrequency(); + Uint64 lastPerfCounter = SDL_GetPerformanceCounter(); + + WindowModule *window = getWindow(); + InputModule *input = getInput(); while (running_) { - mainLoop(); + // 处理窗口事件 + if (window) { + if (!window->pollEvents()) { + quit(); + break; + } + } + + // 计算 deltaTime + Uint64 currentPerfCounter = SDL_GetPerformanceCounter(); + deltaTime_ = + static_cast(currentPerfCounter - lastPerfCounter) / perfFreq; + lastPerfCounter = currentPerfCounter; + + totalTime_ += deltaTime_; + + // 更新 + if (!paused_) { + update(); + } + + // 交换缓冲区 + if (window) { + window->swapBuffers(); + } + + // FPS 限制 - 使用 SDL_Delay + if (!config_.vsync && config_.fpsLimit > 0) { + Uint64 frameEndCounter = SDL_GetPerformanceCounter(); + float frameTime = + static_cast(frameEndCounter - currentPerfCounter) / perfFreq; + float targetTime = 1.0f / config_.fpsLimit; + if (frameTime < targetTime) { + SDL_Delay(static_cast((targetTime - frameTime) * 1000)); + } + } } } @@ -176,80 +167,45 @@ void Application::resume() { if (paused_) { paused_ = false; events::OnResume::emit(); - lastFrameTime_ = getTimeSeconds(); E2D_LOG_INFO("Application resumed"); } } -void Application::mainLoop() { - // 处理窗口事件 - if (windowModule_) { - if (!windowModule_->pollEvents()) { - // 窗口关闭事件 - quit(); - return; - } - } - - 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; - } - - if (!paused_) { - update(); - } - - // 交换缓冲区 - if (windowModule_) { - windowModule_->swapBuffers(); - } - - // 帧率限制 - if (!config_.vsync && config_.fpsLimit > 0) { - double frameEndTime = getTimeSeconds(); - double frameTime = frameEndTime - currentTime; - double target = 1.0 / static_cast(config_.fpsLimit); - if (frameTime < target) { - std::this_thread::sleep_for( - std::chrono::duration(target - frameTime)); - } - } -} - void Application::update() { + // 更新所有模块 + for (auto &module : modules_) { + module->update(deltaTime_); + } + // 通过上下文更新引擎 if (context_) { context_->tick(deltaTime_); } } +WindowModule *Application::getWindow() const { + return getModule(); +} + +InputModule *Application::getInput() const { return getModule(); } + int32 Application::getWindowWidth() const { - if (windowModule_) { - Size size = windowModule_->getSize(); + if (WindowModule *window = getWindow()) { + Size size = window->getSize(); return static_cast(size.w); } return config_.width; } int32 Application::getWindowHeight() const { - if (windowModule_) { - Size size = windowModule_->getSize(); + if (WindowModule *window = getWindow()) { + Size size = window->getSize(); return static_cast(size.h); } return config_.height; } -const char* Application::getWindowTitle() const { +const char *Application::getWindowTitle() const { return config_.title.c_str(); } diff --git a/src/module/module_registry.cpp b/src/module/module_registry.cpp index 1f7bdad..31287ff 100644 --- a/src/module/module_registry.cpp +++ b/src/module/module_registry.cpp @@ -1,153 +1,29 @@ #include -#include #include namespace extra2d { -ModuleRegistry::ModuleRegistry() = default; - -ModuleRegistry::~ModuleRegistry() { - if (inited_) { - shutdownAll(); - } +ModuleRegistry& ModuleRegistry::instance() { + static ModuleRegistry instance; + return instance; } -ModuleRegistry::ModuleRegistry(ModuleRegistry&&) noexcept = default; -ModuleRegistry& ModuleRegistry::operator=(ModuleRegistry&&) noexcept = default; +std::vector> ModuleRegistry::createModules() { + // 按优先级排序(值小的先初始化) + std::vector sorted = registrations_; + std::sort(sorted.begin(), sorted.end(), + [](const ModuleInfo& a, const ModuleInfo& b) { + return a.priority < b.priority; + }); -void ModuleRegistry::registerModule(IModule* module) { - if (!module) { - return; + std::vector> modules; + modules.reserve(sorted.size()); + + for (const auto& info : sorted) { + modules.push_back(info.factory()); } - const char* name = module->name(); - if (!name) { - return; // 名称为空 - } - - // 如果已存在同名模块,先注销旧的 - if (moduleMap_.find(name) != moduleMap_.end()) { - unregisterModule(name); - } - - modules_.push_back(module); - moduleMap_[name] = module; - sorted_ = false; - - // 模块注册事件(暂不发送,避免依赖 events.h) - // event::broadcast(name, module->type()); -} - -void ModuleRegistry::unregisterModule(const char* name) { - if (!name) { - return; - } - - auto it = moduleMap_.find(name); - if (it == moduleMap_.end()) { - return; - } - - IModule* module = it->second; - - // 如果已初始化,先关闭 - if (inited_ && module) { - module->shutdown(); - } - - // 从列表中移除 - modules_.erase( - std::remove(modules_.begin(), modules_.end(), module), - modules_.end() - ); - - moduleMap_.erase(it); -} - -IModule* ModuleRegistry::getModule(const char* name) const { - if (!name) { - return nullptr; - } - - auto it = moduleMap_.find(name); - return it != moduleMap_.end() ? it->second : nullptr; -} - -bool ModuleRegistry::hasModule(const char* name) const { - if (!name) { - return false; - } - return moduleMap_.find(name) != moduleMap_.end(); -} - -std::vector ModuleRegistry::getAllModules() const { - return modules_; -} - -std::vector ModuleRegistry::getModulesByType(ModuleType type) const { - std::vector result; - for (auto* module : modules_) { - if (module && module->type() == type) { - result.push_back(module); - } - } - return result; -} - -bool ModuleRegistry::initAll() { - if (inited_) { - return true; - } - - // 按优先级排序 - sortModules(); - - // 初始化所有模块 - for (auto* module : modules_) { - if (module && !module->init()) { - // 初始化失败,关闭已初始化的模块 - shutdownAll(); - return false; - } - } - - inited_ = true; - return true; -} - -void ModuleRegistry::shutdownAll() { - if (!inited_) { - return; - } - - // 按优先级逆序关闭 - for (auto it = modules_.rbegin(); it != modules_.rend(); ++it) { - if (*it) { - (*it)->shutdown(); - } - } - - inited_ = false; -} - -size_t ModuleRegistry::getModuleCount() const { - return modules_.size(); -} - -void ModuleRegistry::sortModules() { - if (sorted_) { - return; - } - - // 按优先级排序(数值小的优先) - std::sort(modules_.begin(), modules_.end(), - [](IModule* a, IModule* b) { - if (!a || !b) return a < b; - return a->priority() < b->priority(); - } - ); - - sorted_ = true; + return modules; } } // namespace extra2d diff --git a/src/platform/file.cpp b/src/platform/file.cpp deleted file mode 100644 index aa9ea22..0000000 --- a/src/platform/file.cpp +++ /dev/null @@ -1,212 +0,0 @@ -#include -#include -#include -#include -#include - -#ifdef _WIN32 -#include -#include -#define mkdir_impl(path, mode) _mkdir(path) -#else -#include -#include -#define mkdir_impl(path, mode) mkdir(path, mode) -#endif - -namespace extra2d { - -FileModule::FileModule() = default; - -FileModule::~FileModule() = default; - -FileModule::FileModule(FileModule&&) noexcept = default; -FileModule& FileModule::operator=(FileModule&&) noexcept = default; - -bool FileModule::init() { - writableDir_ = SDL_GetPrefPath("Extra2D", "Extra2D"); - if (writableDir_.empty()) { - writableDir_ = "./"; - } - return true; -} - -void FileModule::shutdown() { - // 清理工作 -} - -bool FileModule::exists(const std::string& path) const { - struct stat st; - return stat(path.c_str(), &st) == 0; -} - -bool FileModule::isDir(const std::string& path) const { - struct stat st; - if (stat(path.c_str(), &st) != 0) return false; - return (st.st_mode & S_IFDIR) != 0; -} - -FileData FileModule::read(const std::string& path) const { - FileData result; - - std::ifstream file(path, std::ios::binary | std::ios::ate); - if (!file.is_open()) { - result.error = "Cannot open file: " + path; - return result; - } - - std::streamsize size = file.tellg(); - file.seekg(0, std::ios::beg); - - result.data.resize(static_cast(size)); - if (!file.read(reinterpret_cast(result.data.data()), size)) { - result.error = "Failed to read file: " + path; - return result; - } - - result.ok = true; - return result; -} - -std::string FileModule::readString(const std::string& path) const { - std::ifstream file(path); - if (!file.is_open()) return ""; - - std::stringstream buffer; - buffer << file.rdbuf(); - return buffer.str(); -} - -bool FileModule::write(const std::string& path, const void* data, size_t size) const { - std::ofstream file(path, std::ios::binary); - if (!file.is_open()) return false; - - file.write(static_cast(data), static_cast(size)); - return file.good(); -} - -bool FileModule::writeString(const std::string& path, const std::string& content) const { - return write(path, content.data(), content.size()); -} - -bool FileModule::append(const std::string& path, const void* data, size_t size) const { - std::ofstream file(path, std::ios::binary | std::ios::app); - if (!file.is_open()) return false; - - file.write(static_cast(data), static_cast(size)); - return file.good(); -} - -bool FileModule::remove(const std::string& path) const { - return std::remove(path.c_str()) == 0; -} - -bool FileModule::mkdir(const std::string& path) const { -#ifdef _WIN32 - return mkdir_impl(path.c_str(), 0755) == 0 || errno == EEXIST; -#else - return mkdir_impl(path.c_str(), 0755) == 0 || errno == EEXIST; -#endif -} - -std::vector FileModule::listDir(const std::string& path) const { - std::vector result; - -#ifdef _WIN32 - WIN32_FIND_DATAA findData; - std::string searchPath = path + "\\*"; - HANDLE hFind = FindFirstFileA(searchPath.c_str(), &findData); - - if (hFind == INVALID_HANDLE_VALUE) return result; - - do { - std::string name = findData.cFileName; - if (name == "." || name == "..") continue; - - FileInfo info; - info.name = name; - info.path = join(path, name); - info.isDir = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; - if (!info.isDir) { - info.size = static_cast(findData.nFileSizeLow) | - (static_cast(findData.nFileSizeHigh) << 32); - } - result.push_back(info); - } while (FindNextFileA(hFind, &findData)); - - FindClose(hFind); -#else - DIR* dir = opendir(path.c_str()); - if (!dir) return result; - - struct dirent* entry; - while ((entry = readdir(dir)) != nullptr) { - std::string name = entry->d_name; - if (name == "." || name == "..") continue; - - FileInfo info; - info.name = name; - info.path = join(path, name); - info.isDir = isDir(info.path); - if (!info.isDir) { - info.size = fileSize(info.path); - } - result.push_back(info); - } - - closedir(dir); -#endif - - return result; -} - -int64 FileModule::fileSize(const std::string& path) const { - struct stat st; - if (stat(path.c_str(), &st) != 0) return -1; - return static_cast(st.st_size); -} - -std::string FileModule::ext(const std::string& path) const { - size_t pos = path.find_last_of('.'); - if (pos == std::string::npos || pos == 0) return ""; - - size_t lastSep = path.find_last_of("/\\"); - if (lastSep != std::string::npos && pos < lastSep) return ""; - - return path.substr(pos + 1); -} - -std::string FileModule::fileName(const std::string& path) const { - size_t pos = path.find_last_of("/\\"); - if (pos == std::string::npos) return path; - return path.substr(pos + 1); -} - -std::string FileModule::dirName(const std::string& path) const { - size_t pos = path.find_last_of("/\\"); - if (pos == std::string::npos) return "."; - if (pos == 0) return "/"; - return path.substr(0, pos); -} - -std::string FileModule::join(const std::string& a, const std::string& b) const { - if (a.empty()) return b; - if (b.empty()) return a; - - char last = a.back(); - if (last == '/' || last == '\\') { - return a + b; - } - return a + "/" + b; -} - -std::string FileModule::writableDir() const { - return writableDir_; -} - -std::string FileModule::assetPath(const std::string& relPath) const { - if (assetRoot_.empty()) return relPath; - return join(assetRoot_, relPath); -} - -} // namespace extra2d diff --git a/src/platform/file_module.cpp b/src/platform/file_module.cpp new file mode 100644 index 0000000..6357d1e --- /dev/null +++ b/src/platform/file_module.cpp @@ -0,0 +1,251 @@ +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#define mkdir_impl(path, mode) _mkdir(path) +#else +#include +#include +#define mkdir_impl(path, mode) mkdir(path, mode) +#endif + +#ifdef __SWITCH__ +#include +#endif + +namespace extra2d { + +FileModule::FileModule() = default; + +FileModule::~FileModule() = default; + +FileModule::FileModule(FileModule &&) noexcept = default; +FileModule &FileModule::operator=(FileModule &&) noexcept = default; + +bool FileModule::init() { + writableDir_ = SDL_GetPrefPath("Extra2D", "Extra2D"); + if (writableDir_.empty()) { + writableDir_ = "./"; + } + +#ifdef __SWITCH__ + // 初始化 Switch 的 RomFS + Result rc = romfsInit(); + if (R_SUCCEEDED(rc)) { + E2D_LOG_INFO("RomFS initialized successfully"); + } else { + E2D_LOG_WARN("romfsInit failed: {:#08X}", rc); + } +#endif + + return true; +} + +void FileModule::shutdown() { +#ifdef __SWITCH__ + // 关闭 RomFS + romfsExit(); +#endif +} + +bool FileModule::exists(const std::string &path) const { + struct stat st; + return stat(path.c_str(), &st) == 0; +} + +bool FileModule::isDir(const std::string &path) const { + struct stat st; + if (stat(path.c_str(), &st) != 0) + return false; + return (st.st_mode & S_IFDIR) != 0; +} + +FileData FileModule::read(const std::string &path) const { + FileData result; + + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + result.error = "Cannot open file: " + path; + return result; + } + + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + + result.data.resize(static_cast(size)); + if (!file.read(reinterpret_cast(result.data.data()), size)) { + result.error = "Failed to read file: " + path; + return result; + } + + result.ok = true; + return result; +} + +std::string FileModule::readString(const std::string &path) const { + std::ifstream file(path); + if (!file.is_open()) + return ""; + + std::stringstream buffer; + buffer << file.rdbuf(); + return buffer.str(); +} + +bool FileModule::write(const std::string &path, const void *data, + size_t size) const { + std::ofstream file(path, std::ios::binary); + if (!file.is_open()) + return false; + + file.write(static_cast(data), + static_cast(size)); + return file.good(); +} + +bool FileModule::writeString(const std::string &path, + const std::string &content) const { + return write(path, content.data(), content.size()); +} + +bool FileModule::append(const std::string &path, const void *data, + size_t size) const { + std::ofstream file(path, std::ios::binary | std::ios::app); + if (!file.is_open()) + return false; + + file.write(static_cast(data), + static_cast(size)); + return file.good(); +} + +bool FileModule::remove(const std::string &path) const { + return std::remove(path.c_str()) == 0; +} + +bool FileModule::mkdir(const std::string &path) const { +#ifdef _WIN32 + return mkdir_impl(path.c_str(), 0755) == 0 || errno == EEXIST; +#else + return mkdir_impl(path.c_str(), 0755) == 0 || errno == EEXIST; +#endif +} + +std::vector FileModule::listDir(const std::string &path) const { + std::vector result; + +#ifdef _WIN32 + WIN32_FIND_DATAA findData; + std::string searchPath = path + "\\*"; + HANDLE hFind = FindFirstFileA(searchPath.c_str(), &findData); + + if (hFind == INVALID_HANDLE_VALUE) + return result; + + do { + std::string name = findData.cFileName; + if (name == "." || name == "..") + continue; + + FileInfo info; + info.name = name; + info.path = join(path, name); + info.isDir = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + if (!info.isDir) { + info.size = static_cast(findData.nFileSizeLow) | + (static_cast(findData.nFileSizeHigh) << 32); + } + result.push_back(info); + } while (FindNextFileA(hFind, &findData)); + + FindClose(hFind); +#else + DIR *dir = opendir(path.c_str()); + if (!dir) + return result; + + struct dirent *entry; + while ((entry = readdir(dir)) != nullptr) { + std::string name = entry->d_name; + if (name == "." || name == "..") + continue; + + FileInfo info; + info.name = name; + info.path = join(path, name); + info.isDir = isDir(info.path); + if (!info.isDir) { + info.size = fileSize(info.path); + } + result.push_back(info); + } + + closedir(dir); +#endif + + return result; +} + +int64 FileModule::fileSize(const std::string &path) const { + struct stat st; + if (stat(path.c_str(), &st) != 0) + return -1; + return static_cast(st.st_size); +} + +std::string FileModule::ext(const std::string &path) const { + size_t pos = path.find_last_of('.'); + if (pos == std::string::npos || pos == 0) + return ""; + + size_t lastSep = path.find_last_of("/\\"); + if (lastSep != std::string::npos && pos < lastSep) + return ""; + + return path.substr(pos + 1); +} + +std::string FileModule::fileName(const std::string &path) const { + size_t pos = path.find_last_of("/\\"); + if (pos == std::string::npos) + return path; + return path.substr(pos + 1); +} + +std::string FileModule::dirName(const std::string &path) const { + size_t pos = path.find_last_of("/\\"); + if (pos == std::string::npos) + return "."; + if (pos == 0) + return "/"; + return path.substr(0, pos); +} + +std::string FileModule::join(const std::string &a, const std::string &b) const { + if (a.empty()) + return b; + if (b.empty()) + return a; + + char last = a.back(); + if (last == '/' || last == '\\') { + return a + b; + } + return a + "/" + b; +} + +std::string FileModule::writableDir() const { return writableDir_; } + +std::string FileModule::assetPath(const std::string &relPath) const { + if (assetRoot_.empty()) + return relPath; + return join(assetRoot_, relPath); +} + +} // namespace extra2d diff --git a/src/platform/input.cpp b/src/platform/input_module.cpp similarity index 99% rename from src/platform/input.cpp rename to src/platform/input_module.cpp index 8171310..6e830a1 100644 --- a/src/platform/input.cpp +++ b/src/platform/input_module.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -104,7 +104,9 @@ void InputModule::shutdown() { endedTouches_.clear(); } -void InputModule::update() { +void InputModule::update(float deltaTime) { + (void)deltaTime; // 未使用参数 + std::memcpy(keyPrev_.data(), keyState_.data(), KEY_COUNT); std::memcpy(mousePrev_.data(), mouseState_.data(), static_cast(MouseBtn::Count)); diff --git a/src/platform/sdl2.cpp b/src/platform/sdl2.cpp deleted file mode 100644 index ae9a903..0000000 --- a/src/platform/sdl2.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include - -namespace extra2d { - -bool Sdl2::inited_ = false; - -bool Sdl2::initCore() { - if (inited_) return true; - - if (SDL_Init(SDL_INIT_EVENTS) != 0) { - return false; - } - - inited_ = true; - return true; -} - -bool Sdl2::initVideo() { - if (!initCore()) return false; - - if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) { - return false; - } - - return true; -} - -bool Sdl2::initAudio() { - if (!initCore()) return false; - - if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) { - return false; - } - - return true; -} - -bool Sdl2::initGamepad() { - if (!initCore()) return false; - - if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0) { - return false; - } - - return true; -} - -bool Sdl2::initAll() { - if (inited_) return true; - - if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { - return false; - } - - inited_ = true; - return true; -} - -void Sdl2::shutdown() { - if (inited_) { - SDL_Quit(); - inited_ = false; - } -} - -} // namespace extra2d diff --git a/src/platform/window.cpp b/src/platform/window_module.cpp similarity index 96% rename from src/platform/window.cpp rename to src/platform/window_module.cpp index 9b08a53..2a7b573 100644 --- a/src/platform/window.cpp +++ b/src/platform/window_module.cpp @@ -1,5 +1,6 @@ -#include +#include #include +#include #include #include @@ -49,6 +50,9 @@ bool WindowModule::init() { SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + // 监听模块配置事件 + events::OnModuleConfig::subscribe(this, &WindowModule::onModuleConfig); + return true; } diff --git a/xmake/engine.lua b/xmake/engine.lua index bf0f4e5..f6ffd61 100644 --- a/xmake/engine.lua +++ b/xmake/engine.lua @@ -13,7 +13,7 @@ function define_extra2d_engine() target("extra2d") set_kind("static") - add_files("src/**.cpp|core/*.cpp|module/module_manager.cpp|plugin/plugin_manager.cpp") + add_files("src/**.cpp|core/*.cpp|module/module_manager.cpp|plugin/plugin_manager.cpp|platform/window.cpp|platform/input.cpp") add_files("third_party/glad/src/glad.c") add_includedirs("include", {public = true})