refactor(引擎核心): 重构模块系统并引入自动注册机制
重构引擎核心模块系统,使用新的 Module 基类替代旧版 IModule 接口 新增模块自动注册机制,通过 E2D_REGISTER_MODULE 宏实现模块注册 将窗口、文件、定时器等模块迁移到新系统,支持配置事件驱动初始化 移除旧版 SDL2 封装和模块管理器,简化应用程序初始化流程
This commit is contained in:
parent
ebf73a4492
commit
f9be301dae
|
|
@ -1,8 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <types/base/types.h>
|
||||
#include <config/app_config.h>
|
||||
#include <module/module.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
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<typename T>
|
||||
T* getModule() const {
|
||||
for (const auto& module : modules_) {
|
||||
if (auto* ptr = dynamic_cast<T*>(module.get())) {
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取窗口宽度
|
||||
|
|
@ -142,12 +141,11 @@ public:
|
|||
private:
|
||||
Application();
|
||||
|
||||
void mainLoop();
|
||||
void update();
|
||||
void initModules();
|
||||
|
||||
std::unique_ptr<Context> context_;
|
||||
std::unique_ptr<WindowModule> windowModule_;
|
||||
std::unique_ptr<InputModule> inputModule_;
|
||||
std::vector<std::unique_ptr<Module>> 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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <types/base/types.h>
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <types/base/types.h>
|
||||
|
||||
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
|
||||
|
|
@ -160,3 +160,31 @@
|
|||
arg5, arg6, arg7); \
|
||||
} \
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 声明模板事件(支持任意配置类型)
|
||||
* @param EventName 事件名称
|
||||
* @param BusName 总线名称
|
||||
*
|
||||
* 使用示例:
|
||||
* DECLARE_EVENT_T(OnModuleInit, Engine)
|
||||
*
|
||||
* // 发送事件
|
||||
* OnModuleInit::emit<MyConfig>(myConfig);
|
||||
*
|
||||
* // 监听事件
|
||||
* OnModuleInit::subscribe(this, &MyModule::onInit);
|
||||
*/
|
||||
#define DECLARE_EVENT_T(EventName, BusName) \
|
||||
template <typename ConfigT> \
|
||||
struct EventName \
|
||||
: ::extra2d::event::EventTrait<E2D_EVENT_BUS_NAME_(BusName), \
|
||||
const ConfigT &> { \
|
||||
using Listener = ::extra2d::event::Listener<EventName<ConfigT>>; \
|
||||
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<EventName<ConfigT>>(config); \
|
||||
} \
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
#include <event/event_bus_macros.h>
|
||||
#include <types/base/types.h>
|
||||
|
||||
|
||||
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 窗口显示事件
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@
|
|||
#include <utils/logger.h>
|
||||
#include <utils/random.h>
|
||||
|
||||
// Config
|
||||
#include <config/app_config.h>
|
||||
#include <config/window_config.h>
|
||||
|
||||
// Application
|
||||
#include <app/application.h>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
#include <types/base/types.h>
|
||||
#include <string>
|
||||
|
||||
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
|
||||
|
|
@ -1,97 +1,111 @@
|
|||
#pragma once
|
||||
|
||||
#include <module/imodule.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <module/module.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <typeindex>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 模块注册表 - 非单例
|
||||
* @brief 模块工厂函数类型
|
||||
*/
|
||||
using ModuleFactory = std::function<std::unique_ptr<Module>()>;
|
||||
|
||||
/**
|
||||
* @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 <typename T>
|
||||
void registerModule(const char *name, int32 priority = 100) {
|
||||
static_assert(std::is_base_of_v<Module, T>, "T must inherit from Module");
|
||||
|
||||
ModuleInfo info{
|
||||
name, []() -> std::unique_ptr<Module> { return std::make_unique<T>(); },
|
||||
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<std::unique_ptr<Module>> createModules();
|
||||
|
||||
/**
|
||||
* @brief 检查模块是否存在
|
||||
* @param name 模块名称
|
||||
* @return 是否存在
|
||||
* @brief 获取已注册的模块信息列表
|
||||
*/
|
||||
bool hasModule(const char *name) const;
|
||||
|
||||
/**
|
||||
* @brief 获取所有模块
|
||||
* @return 模块列表
|
||||
*/
|
||||
std::vector<IModule *> getAllModules() const;
|
||||
|
||||
/**
|
||||
* @brief 获取指定类型的所有模块
|
||||
* @param type 模块类型
|
||||
* @return 模块列表
|
||||
*/
|
||||
std::vector<IModule *> getModulesByType(ModuleType type) const;
|
||||
|
||||
/**
|
||||
* @brief 初始化所有模块(按优先级排序)
|
||||
* @return 是否全部初始化成功
|
||||
*/
|
||||
bool initAll();
|
||||
|
||||
/**
|
||||
* @brief 关闭所有模块(按优先级逆序)
|
||||
*/
|
||||
void shutdownAll();
|
||||
|
||||
/**
|
||||
* @brief 获取模块数量
|
||||
* @return 模块数量
|
||||
*/
|
||||
size_t getModuleCount() const;
|
||||
const std::vector<ModuleInfo> &getRegistrations() const {
|
||||
return registrations_;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 按优先级排序模块
|
||||
*/
|
||||
void sortModules();
|
||||
|
||||
std::vector<IModule *> modules_;
|
||||
std::unordered_map<std::string, IModule *> moduleMap_;
|
||||
bool sorted_ = false;
|
||||
bool inited_ = false;
|
||||
ModuleRegistry() = default;
|
||||
std::vector<ModuleInfo> registrations_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 自动模块注册辅助类
|
||||
*/
|
||||
template <typename T> class ModuleRegistrar {
|
||||
public:
|
||||
ModuleRegistrar(const char *name, int32 priority = 100) {
|
||||
ModuleRegistry::instance().registerModule<T>(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<ClassName> _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
|
||||
|
|
|
|||
|
|
@ -1,149 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <module/imodule.h>
|
||||
#include <types/base/types.h>
|
||||
#include <types/const/priority.h>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <memory>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 定时器句柄
|
||||
*/
|
||||
using TimerId = uint32;
|
||||
constexpr TimerId INVALID_TIMER_ID = 0;
|
||||
|
||||
/**
|
||||
* @brief 定时器回调类型
|
||||
*/
|
||||
using TimerCallback = std::function<void()>;
|
||||
using TimerUpdateCallback = std::function<void(float)>;
|
||||
|
||||
/**
|
||||
* @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<TimerId, std::unique_ptr<TimerInfo>> timers_;
|
||||
std::vector<TimerId> pendingRemove_;
|
||||
TimerId nextId_ = 1;
|
||||
float timeScale_ = 1.0f;
|
||||
bool inUpdate_ = false;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <module/imodule.h>
|
||||
#include <module/module.h>
|
||||
#include <module/module_registry.h>
|
||||
#include <types/base/types.h>
|
||||
#include <types/const/priority.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -1,378 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <SDL.h>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <module/imodule.h>
|
||||
#include <types/base/types.h>
|
||||
#include <types/const/priority.h>
|
||||
#include <vector>
|
||||
|
||||
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<void(Key)>;
|
||||
using MouseBtnCb = std::function<void(MouseBtn, int32 x, int32 y)>;
|
||||
using TouchCb = std::function<void(const TouchPoint&)>;
|
||||
|
||||
/**
|
||||
* @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<TouchPoint>& 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<uint8, KEY_COUNT> keyState_{};
|
||||
std::array<uint8, KEY_COUNT> keyPrev_{};
|
||||
|
||||
int32 mouseX_ = 0;
|
||||
int32 mouseY_ = 0;
|
||||
int32 mouseWheel_ = 0;
|
||||
std::array<uint8, static_cast<size_t>(MouseBtn::Count)> mouseState_{};
|
||||
std::array<uint8, static_cast<size_t>(MouseBtn::Count)> mousePrev_{};
|
||||
|
||||
std::vector<TouchPoint> activeTouches_;
|
||||
std::vector<TouchPoint> endedTouches_;
|
||||
|
||||
SDL_GameController* gamepads_[MAX_GAMEPADS] = {};
|
||||
std::array<uint8, static_cast<size_t>(GamepadBtn::Count)> padState_[MAX_GAMEPADS];
|
||||
std::array<uint8, static_cast<size_t>(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
|
||||
|
|
@ -0,0 +1,376 @@
|
|||
#pragma once
|
||||
|
||||
#include <SDL.h>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <module/module.h>
|
||||
#include <module/module_registry.h>
|
||||
#include <types/base/types.h>
|
||||
#include <vector>
|
||||
|
||||
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<void(Key)>;
|
||||
using MouseBtnCb = std::function<void(MouseBtn, int32 x, int32 y)>;
|
||||
using TouchCb = std::function<void(const TouchPoint &)>;
|
||||
|
||||
/**
|
||||
* @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<TouchPoint> &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<uint8, KEY_COUNT> keyState_{};
|
||||
std::array<uint8, KEY_COUNT> keyPrev_{};
|
||||
|
||||
int32 mouseX_ = 0;
|
||||
int32 mouseY_ = 0;
|
||||
int32 mouseWheel_ = 0;
|
||||
std::array<uint8, static_cast<size_t>(MouseBtn::Count)> mouseState_{};
|
||||
std::array<uint8, static_cast<size_t>(MouseBtn::Count)> mousePrev_{};
|
||||
|
||||
std::vector<TouchPoint> activeTouches_;
|
||||
std::vector<TouchPoint> endedTouches_;
|
||||
|
||||
SDL_GameController *gamepads_[MAX_GAMEPADS] = {};
|
||||
std::array<uint8, static_cast<size_t>(GamepadBtn::Count)>
|
||||
padState_[MAX_GAMEPADS];
|
||||
std::array<uint8, static_cast<size_t>(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
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL_syswm.h>
|
||||
|
||||
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
|
||||
|
|
@ -1,168 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <SDL.h>
|
||||
#include <functional>
|
||||
#include <module/imodule.h>
|
||||
#include <string>
|
||||
#include <types/base/types.h>
|
||||
#include <types/const/priority.h>
|
||||
#include <types/math/size.h>
|
||||
#include <types/math/vec2.h>
|
||||
|
||||
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<void(int32 w, int32 h)>;
|
||||
using CloseCb = std::function<void()>;
|
||||
|
||||
/**
|
||||
* @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
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
#pragma once
|
||||
|
||||
#include <SDL.h>
|
||||
#include <config/app_config.h>
|
||||
#include <config/window_config.h>
|
||||
#include <functional>
|
||||
#include <module/module.h>
|
||||
#include <module/module_registry.h>
|
||||
#include <type_traits>
|
||||
#include <types/math/size.h>
|
||||
#include <types/math/vec2.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 窗口事件回调
|
||||
*/
|
||||
using ResizeCb = std::function<void(int32 w, int32 h)>;
|
||||
using CloseCb = std::function<void()>;
|
||||
|
||||
/**
|
||||
* @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 <typename ConfigT> void onModuleConfig(const ConfigT &config) {
|
||||
// 只处理 AppConfig 类型
|
||||
if constexpr (std::is_same_v<ConfigT, AppConfig>) {
|
||||
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
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <module/module.h>
|
||||
#include <module/module_registry.h>
|
||||
#include <types/base/types.h>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 定时器句柄
|
||||
*/
|
||||
using TimerId = uint32;
|
||||
constexpr TimerId INVALID_TIMER_ID = 0;
|
||||
|
||||
/**
|
||||
* @brief 定时器回调类型
|
||||
*/
|
||||
using TimerCallback = std::function<void()>;
|
||||
using TimerUpdateCallback = std::function<void(float)>;
|
||||
|
||||
/**
|
||||
* @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<TimerId, std::unique_ptr<TimerInfo>> timers_;
|
||||
std::vector<TimerId> pendingRemove_;
|
||||
TimerId nextId_ = 1;
|
||||
float timeScale_ = 1.0f;
|
||||
bool inUpdate_ = false;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,49 +1,25 @@
|
|||
#include <app/application.h>
|
||||
#include <context/context.h>
|
||||
#include <event/events.h>
|
||||
#include <platform/sdl2.h>
|
||||
#include <platform/window.h>
|
||||
#include <platform/input.h>
|
||||
#include <module/module_registry.h>
|
||||
#include <platform/input_module.h>
|
||||
#include <platform/window_module.h>
|
||||
#include <utils/logger.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#ifdef __SWITCH__
|
||||
#include <switch.h>
|
||||
#endif
|
||||
#include <SDL.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 获取当前时间(秒)
|
||||
*/
|
||||
static double getTimeSeconds() {
|
||||
#ifdef __SWITCH__
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return static_cast<double>(ts.tv_sec) +
|
||||
static_cast<double>(ts.tv_nsec) / 1000000000.0;
|
||||
#else
|
||||
using namespace std::chrono;
|
||||
auto now = steady_clock::now();
|
||||
auto duration = now.time_since_epoch();
|
||||
return duration_cast<std::chrono::duration<double>>(duration).count();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::unique_ptr<Application> Application::create() {
|
||||
return std::unique_ptr<Application>(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<WindowModule>();
|
||||
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<InputModule>();
|
||||
// 通过事件总线发送配置给所有监听模块
|
||||
events::OnModuleConfig<AppConfig>::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<float>(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<float>(frameEndCounter - currentPerfCounter) / perfFreq;
|
||||
float targetTime = 1.0f / config_.fpsLimit;
|
||||
if (frameTime < targetTime) {
|
||||
SDL_Delay(static_cast<Uint32>((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<float>(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<double>(config_.fpsLimit);
|
||||
if (frameTime < target) {
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::duration<double>(target - frameTime));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::update() {
|
||||
// 更新所有模块
|
||||
for (auto &module : modules_) {
|
||||
module->update(deltaTime_);
|
||||
}
|
||||
|
||||
// 通过上下文更新引擎
|
||||
if (context_) {
|
||||
context_->tick(deltaTime_);
|
||||
}
|
||||
}
|
||||
|
||||
WindowModule *Application::getWindow() const {
|
||||
return getModule<WindowModule>();
|
||||
}
|
||||
|
||||
InputModule *Application::getInput() const { return getModule<InputModule>(); }
|
||||
|
||||
int32 Application::getWindowWidth() const {
|
||||
if (windowModule_) {
|
||||
Size size = windowModule_->getSize();
|
||||
if (WindowModule *window = getWindow()) {
|
||||
Size size = window->getSize();
|
||||
return static_cast<int32>(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<int32>(size.h);
|
||||
}
|
||||
return config_.height;
|
||||
}
|
||||
|
||||
const char* Application::getWindowTitle() const {
|
||||
const char *Application::getWindowTitle() const {
|
||||
return config_.title.c_str();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,153 +1,29 @@
|
|||
#include <module/module_registry.h>
|
||||
#include <event/event_bus.h>
|
||||
#include <algorithm>
|
||||
|
||||
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<std::unique_ptr<Module>> ModuleRegistry::createModules() {
|
||||
// 按优先级排序(值小的先初始化)
|
||||
std::vector<ModuleInfo> 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<std::unique_ptr<Module>> 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<events::OnModuleRegistered>(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<IModule*> ModuleRegistry::getAllModules() const {
|
||||
return modules_;
|
||||
}
|
||||
|
||||
std::vector<IModule*> ModuleRegistry::getModulesByType(ModuleType type) const {
|
||||
std::vector<IModule*> 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
|
||||
|
|
|
|||
|
|
@ -1,212 +0,0 @@
|
|||
#include <platform/file.h>
|
||||
#include <SDL.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#include <windows.h>
|
||||
#define mkdir_impl(path, mode) _mkdir(path)
|
||||
#else
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#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_t>(size));
|
||||
if (!file.read(reinterpret_cast<char*>(result.data.data()), size)) {
|
||||
result.error = "Failed to read file: " + path;
|
||||
return result;
|
||||
}
|
||||
|
||||
result.ok = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string FileModule::readString(const std::string& path) const {
|
||||
std::ifstream file(path);
|
||||
if (!file.is_open()) return "";
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
bool FileModule::write(const std::string& path, const void* data, size_t size) const {
|
||||
std::ofstream file(path, std::ios::binary);
|
||||
if (!file.is_open()) return false;
|
||||
|
||||
file.write(static_cast<const char*>(data), static_cast<std::streamsize>(size));
|
||||
return file.good();
|
||||
}
|
||||
|
||||
bool FileModule::writeString(const std::string& path, const std::string& content) const {
|
||||
return write(path, content.data(), content.size());
|
||||
}
|
||||
|
||||
bool FileModule::append(const std::string& path, const void* data, size_t size) const {
|
||||
std::ofstream file(path, std::ios::binary | std::ios::app);
|
||||
if (!file.is_open()) return false;
|
||||
|
||||
file.write(static_cast<const char*>(data), static_cast<std::streamsize>(size));
|
||||
return file.good();
|
||||
}
|
||||
|
||||
bool FileModule::remove(const std::string& path) const {
|
||||
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<FileInfo> FileModule::listDir(const std::string& path) const {
|
||||
std::vector<FileInfo> 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<int64>(findData.nFileSizeLow) |
|
||||
(static_cast<int64>(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<int64>(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
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
#include <SDL.h>
|
||||
#include <fstream>
|
||||
#include <platform/file_module.h>
|
||||
#include <sstream>
|
||||
#include <sys/stat.h>
|
||||
#include <utils/logger.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#include <windows.h>
|
||||
#define mkdir_impl(path, mode) _mkdir(path)
|
||||
#else
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#define mkdir_impl(path, mode) mkdir(path, mode)
|
||||
#endif
|
||||
|
||||
#ifdef __SWITCH__
|
||||
#include <switch.h>
|
||||
#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_t>(size));
|
||||
if (!file.read(reinterpret_cast<char *>(result.data.data()), size)) {
|
||||
result.error = "Failed to read file: " + path;
|
||||
return result;
|
||||
}
|
||||
|
||||
result.ok = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string FileModule::readString(const std::string &path) const {
|
||||
std::ifstream file(path);
|
||||
if (!file.is_open())
|
||||
return "";
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
bool FileModule::write(const std::string &path, const void *data,
|
||||
size_t size) const {
|
||||
std::ofstream file(path, std::ios::binary);
|
||||
if (!file.is_open())
|
||||
return false;
|
||||
|
||||
file.write(static_cast<const char *>(data),
|
||||
static_cast<std::streamsize>(size));
|
||||
return file.good();
|
||||
}
|
||||
|
||||
bool FileModule::writeString(const std::string &path,
|
||||
const std::string &content) const {
|
||||
return write(path, content.data(), content.size());
|
||||
}
|
||||
|
||||
bool FileModule::append(const std::string &path, const void *data,
|
||||
size_t size) const {
|
||||
std::ofstream file(path, std::ios::binary | std::ios::app);
|
||||
if (!file.is_open())
|
||||
return false;
|
||||
|
||||
file.write(static_cast<const char *>(data),
|
||||
static_cast<std::streamsize>(size));
|
||||
return file.good();
|
||||
}
|
||||
|
||||
bool FileModule::remove(const std::string &path) const {
|
||||
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<FileInfo> FileModule::listDir(const std::string &path) const {
|
||||
std::vector<FileInfo> 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<int64>(findData.nFileSizeLow) |
|
||||
(static_cast<int64>(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<int64>(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
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#include <platform/input.h>
|
||||
#include <platform/input_module.h>
|
||||
#include <event/events.h>
|
||||
#include <SDL.h>
|
||||
#include <cstring>
|
||||
|
|
@ -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<size_t>(MouseBtn::Count));
|
||||
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
#include <platform/sdl2.h>
|
||||
|
||||
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
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
#include <platform/window.h>
|
||||
#include <platform/window_module.h>
|
||||
#include <platform/sdl2.h>
|
||||
#include <config/app_config.h>
|
||||
#include <event/events.h>
|
||||
#include <utils/logger.h>
|
||||
|
||||
|
|
@ -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<AppConfig>::subscribe(this, &WindowModule::onModuleConfig<AppConfig>);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -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})
|
||||
|
|
|
|||
Loading…
Reference in New Issue