refactor(引擎核心): 重构模块系统并引入自动注册机制
重构引擎核心模块系统,使用新的 Module 基类替代旧版 IModule 接口 新增模块自动注册机制,通过 E2D_REGISTER_MODULE 宏实现模块注册 将窗口、文件、定时器等模块迁移到新系统,支持配置事件驱动初始化 移除旧版 SDL2 封装和模块管理器,简化应用程序初始化流程
This commit is contained in:
parent
ebf73a4492
commit
f9be301dae
|
|
@ -1,8 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <config/app_config.h>
|
||||||
#include <types/base/types.h>
|
#include <module/module.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
@ -12,27 +13,10 @@ class WindowModule;
|
||||||
class InputModule;
|
class InputModule;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 应用程序配置
|
* @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 应用程序主控类
|
|
||||||
*
|
*
|
||||||
* 管理应用程序生命周期、窗口和主循环
|
* 管理应用程序生命周期、窗口和主循环
|
||||||
* 基于新的 Context 架构,支持多实例
|
* 自动管理模块的创建和销毁
|
||||||
*/
|
*/
|
||||||
class Application {
|
class Application {
|
||||||
public:
|
public:
|
||||||
|
|
@ -102,7 +86,7 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief 获取当前 FPS
|
* @brief 获取当前 FPS
|
||||||
*/
|
*/
|
||||||
int32 fps() const { return currentFps_; }
|
int32 fps() const { return 0; } // TODO: 使用 SDL 计算 FPS
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取配置
|
* @brief 获取配置
|
||||||
|
|
@ -117,12 +101,27 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief 获取窗口模块
|
* @brief 获取窗口模块
|
||||||
*/
|
*/
|
||||||
WindowModule* getWindow() const { return windowModule_.get(); }
|
WindowModule* getWindow() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取输入模块
|
* @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 获取窗口宽度
|
* @brief 获取窗口宽度
|
||||||
|
|
@ -142,12 +141,11 @@ public:
|
||||||
private:
|
private:
|
||||||
Application();
|
Application();
|
||||||
|
|
||||||
void mainLoop();
|
|
||||||
void update();
|
void update();
|
||||||
|
void initModules();
|
||||||
|
|
||||||
std::unique_ptr<Context> context_;
|
std::unique_ptr<Context> context_;
|
||||||
std::unique_ptr<WindowModule> windowModule_;
|
std::vector<std::unique_ptr<Module>> modules_;
|
||||||
std::unique_ptr<InputModule> inputModule_;
|
|
||||||
|
|
||||||
AppConfig config_;
|
AppConfig config_;
|
||||||
|
|
||||||
|
|
@ -158,10 +156,6 @@ private:
|
||||||
|
|
||||||
float deltaTime_ = 0.0f;
|
float deltaTime_ = 0.0f;
|
||||||
float totalTime_ = 0.0f;
|
float totalTime_ = 0.0f;
|
||||||
double lastFrameTime_ = 0.0;
|
|
||||||
int32 frameCount_ = 0;
|
|
||||||
float fpsTimer_ = 0.0f;
|
|
||||||
int32 currentFps_ = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // 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); \
|
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 <event/event_bus_macros.h>
|
||||||
#include <types/base/types.h>
|
#include <types/base/types.h>
|
||||||
|
|
||||||
|
|
||||||
namespace extra2d::events {
|
namespace extra2d::events {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -79,6 +78,19 @@ DECLARE_EVENT_0(OnFocus, Engine)
|
||||||
*/
|
*/
|
||||||
DECLARE_EVENT_0(OnBlur, 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 窗口显示事件
|
* @brief 窗口显示事件
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,10 @@
|
||||||
#include <utils/logger.h>
|
#include <utils/logger.h>
|
||||||
#include <utils/random.h>
|
#include <utils/random.h>
|
||||||
|
|
||||||
|
// Config
|
||||||
|
#include <config/app_config.h>
|
||||||
|
#include <config/window_config.h>
|
||||||
|
|
||||||
// Application
|
// Application
|
||||||
#include <app/application.h>
|
#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
|
#pragma once
|
||||||
|
|
||||||
#include <module/imodule.h>
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <module/module.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <typeindex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace extra2d {
|
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 {
|
class ModuleRegistry {
|
||||||
public:
|
public:
|
||||||
ModuleRegistry();
|
|
||||||
~ModuleRegistry();
|
|
||||||
|
|
||||||
// 禁止拷贝
|
|
||||||
ModuleRegistry(const ModuleRegistry &) = delete;
|
|
||||||
ModuleRegistry &operator=(const ModuleRegistry &) = delete;
|
|
||||||
|
|
||||||
// 允许移动
|
|
||||||
ModuleRegistry(ModuleRegistry &&) noexcept;
|
|
||||||
ModuleRegistry &operator=(ModuleRegistry &&) noexcept;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 注册模块
|
* @brief 获取全局注册表实例
|
||||||
* @param module 模块实例指针(不由注册表管理生命周期)
|
|
||||||
*/
|
*/
|
||||||
void registerModule(IModule *module);
|
static ModuleRegistry &instance();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 注销模块
|
* @brief 注册模块类型
|
||||||
|
* @tparam T 模块类型
|
||||||
* @param name 模块名称
|
* @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 获取模块
|
* @brief 创建所有已注册的模块
|
||||||
* @param name 模块名称
|
* @return 创建的模块列表(已按优先级排序)
|
||||||
* @return 模块指针,不存在返回 nullptr
|
|
||||||
*/
|
*/
|
||||||
IModule *getModule(const char *name) const;
|
std::vector<std::unique_ptr<Module>> createModules();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 检查模块是否存在
|
* @brief 获取已注册的模块信息列表
|
||||||
* @param name 模块名称
|
|
||||||
* @return 是否存在
|
|
||||||
*/
|
*/
|
||||||
bool hasModule(const char *name) const;
|
const std::vector<ModuleInfo> &getRegistrations() const {
|
||||||
|
return registrations_;
|
||||||
/**
|
}
|
||||||
* @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;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
ModuleRegistry() = default;
|
||||||
* @brief 按优先级排序模块
|
std::vector<ModuleInfo> registrations_;
|
||||||
*/
|
|
||||||
void sortModules();
|
|
||||||
|
|
||||||
std::vector<IModule *> modules_;
|
|
||||||
std::unordered_map<std::string, IModule *> moduleMap_;
|
|
||||||
bool sorted_ = false;
|
|
||||||
bool inited_ = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
} // 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
|
#pragma once
|
||||||
|
|
||||||
#include <module/imodule.h>
|
#include <module/module.h>
|
||||||
|
#include <module/module_registry.h>
|
||||||
#include <types/base/types.h>
|
#include <types/base/types.h>
|
||||||
#include <types/const/priority.h>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
|
@ -37,7 +37,10 @@ struct FileData {
|
||||||
* 提供跨平台文件系统操作
|
* 提供跨平台文件系统操作
|
||||||
* 非单例设计,通过 Context 管理生命周期
|
* 非单例设计,通过 Context 管理生命周期
|
||||||
*/
|
*/
|
||||||
class FileModule : public IModule {
|
class FileModule : public Module {
|
||||||
|
// 自动注册到模块系统,优先级为 20(系统模块)
|
||||||
|
E2D_REGISTER_MODULE(FileModule, "File", 20)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FileModule();
|
FileModule();
|
||||||
~FileModule() override;
|
~FileModule() override;
|
||||||
|
|
@ -50,10 +53,7 @@ public:
|
||||||
FileModule(FileModule&&) noexcept;
|
FileModule(FileModule&&) noexcept;
|
||||||
FileModule& operator=(FileModule&&) noexcept;
|
FileModule& operator=(FileModule&&) noexcept;
|
||||||
|
|
||||||
// IModule 接口实现
|
// Module 接口实现
|
||||||
const char* name() const override { return "File"; }
|
|
||||||
ModuleType type() const override { return ModuleType::System; }
|
|
||||||
int priority() const override { return Pri::File; }
|
|
||||||
bool init() override;
|
bool init() override;
|
||||||
void shutdown() 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 <app/application.h>
|
||||||
#include <context/context.h>
|
#include <context/context.h>
|
||||||
#include <event/events.h>
|
#include <event/events.h>
|
||||||
#include <platform/sdl2.h>
|
#include <module/module_registry.h>
|
||||||
#include <platform/window.h>
|
#include <platform/input_module.h>
|
||||||
#include <platform/input.h>
|
#include <platform/window_module.h>
|
||||||
#include <utils/logger.h>
|
#include <utils/logger.h>
|
||||||
|
|
||||||
#include <chrono>
|
#include <SDL.h>
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
#include <switch.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace extra2d {
|
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() {
|
std::unique_ptr<Application> Application::create() {
|
||||||
return std::unique_ptr<Application>(new Application());
|
return std::unique_ptr<Application>(new Application());
|
||||||
}
|
}
|
||||||
|
|
||||||
Application::Application() = default;
|
Application::Application() = default;
|
||||||
|
|
||||||
Application::~Application() {
|
Application::~Application() { shutdown(); }
|
||||||
shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
Application::Application(Application&&) noexcept = default;
|
Application::Application(Application &&) noexcept = default;
|
||||||
Application& Application::operator=(Application&&) noexcept = default;
|
Application &Application::operator=(Application &&) noexcept = default;
|
||||||
|
|
||||||
bool Application::init(const AppConfig &config) {
|
bool Application::init(const AppConfig &config) {
|
||||||
if (initialized_) {
|
if (initialized_) {
|
||||||
|
|
@ -53,26 +29,6 @@ bool Application::init(const AppConfig &config) {
|
||||||
|
|
||||||
config_ = 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();
|
context_ = Context::create();
|
||||||
if (!context_) {
|
if (!context_) {
|
||||||
|
|
@ -86,26 +42,11 @@ bool Application::init(const AppConfig &config) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建窗口模块
|
// 自动创建所有已注册的模块
|
||||||
windowModule_ = std::make_unique<WindowModule>();
|
initModules();
|
||||||
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;
|
|
||||||
|
|
||||||
if (!windowModule_->create(wcfg)) {
|
// 通过事件总线发送配置给所有监听模块
|
||||||
E2D_LOG_ERROR("Failed to create window");
|
events::OnModuleConfig<AppConfig>::emit(config);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
windowModule_->setVisible(true);
|
|
||||||
|
|
||||||
// 创建输入模块
|
|
||||||
inputModule_ = std::make_unique<InputModule>();
|
|
||||||
|
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
running_ = true;
|
running_ = true;
|
||||||
|
|
@ -113,11 +54,26 @@ bool Application::init(const AppConfig &config) {
|
||||||
events::OnInit::emit();
|
events::OnInit::emit();
|
||||||
|
|
||||||
E2D_LOG_INFO("Application initialized successfully");
|
E2D_LOG_INFO("Application initialized successfully");
|
||||||
E2D_LOG_INFO("Window: {}x{}, Fullscreen: {}, VSync: {}", config_.width,
|
E2D_LOG_INFO("Window: {}x{}, Fullscreen: {}, VSync: {}", config.width,
|
||||||
config_.height, config_.fullscreen, config_.vsync);
|
config.height, config.fullscreen, config.vsync);
|
||||||
return true;
|
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() {
|
void Application::shutdown() {
|
||||||
if (!initialized_)
|
if (!initialized_)
|
||||||
return;
|
return;
|
||||||
|
|
@ -126,20 +82,15 @@ void Application::shutdown() {
|
||||||
|
|
||||||
E2D_LOG_INFO("Shutting down application...");
|
E2D_LOG_INFO("Shutting down application...");
|
||||||
|
|
||||||
// 智能指针自动销毁窗口和输入模块
|
// 按相反顺序销毁模块
|
||||||
inputModule_.reset();
|
for (auto it = modules_.rbegin(); it != modules_.rend(); ++it) {
|
||||||
windowModule_.reset();
|
(*it)->shutdown();
|
||||||
|
}
|
||||||
|
modules_.clear();
|
||||||
|
|
||||||
// 关闭上下文
|
// 关闭上下文
|
||||||
context_.reset();
|
context_.reset();
|
||||||
|
|
||||||
Sdl2::shutdown();
|
|
||||||
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
romfsExit();
|
|
||||||
socketExit();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
initialized_ = false;
|
initialized_ = false;
|
||||||
running_ = false;
|
running_ = false;
|
||||||
|
|
||||||
|
|
@ -152,10 +103,50 @@ void Application::run() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastFrameTime_ = getTimeSeconds();
|
// 使用 SDL 的高精度计时器
|
||||||
|
Uint64 perfFreq = SDL_GetPerformanceFrequency();
|
||||||
|
Uint64 lastPerfCounter = SDL_GetPerformanceCounter();
|
||||||
|
|
||||||
|
WindowModule *window = getWindow();
|
||||||
|
InputModule *input = getInput();
|
||||||
|
|
||||||
while (running_) {
|
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_) {
|
if (paused_) {
|
||||||
paused_ = false;
|
paused_ = false;
|
||||||
events::OnResume::emit();
|
events::OnResume::emit();
|
||||||
lastFrameTime_ = getTimeSeconds();
|
|
||||||
E2D_LOG_INFO("Application resumed");
|
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() {
|
void Application::update() {
|
||||||
|
// 更新所有模块
|
||||||
|
for (auto &module : modules_) {
|
||||||
|
module->update(deltaTime_);
|
||||||
|
}
|
||||||
|
|
||||||
// 通过上下文更新引擎
|
// 通过上下文更新引擎
|
||||||
if (context_) {
|
if (context_) {
|
||||||
context_->tick(deltaTime_);
|
context_->tick(deltaTime_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WindowModule *Application::getWindow() const {
|
||||||
|
return getModule<WindowModule>();
|
||||||
|
}
|
||||||
|
|
||||||
|
InputModule *Application::getInput() const { return getModule<InputModule>(); }
|
||||||
|
|
||||||
int32 Application::getWindowWidth() const {
|
int32 Application::getWindowWidth() const {
|
||||||
if (windowModule_) {
|
if (WindowModule *window = getWindow()) {
|
||||||
Size size = windowModule_->getSize();
|
Size size = window->getSize();
|
||||||
return static_cast<int32>(size.w);
|
return static_cast<int32>(size.w);
|
||||||
}
|
}
|
||||||
return config_.width;
|
return config_.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 Application::getWindowHeight() const {
|
int32 Application::getWindowHeight() const {
|
||||||
if (windowModule_) {
|
if (WindowModule *window = getWindow()) {
|
||||||
Size size = windowModule_->getSize();
|
Size size = window->getSize();
|
||||||
return static_cast<int32>(size.h);
|
return static_cast<int32>(size.h);
|
||||||
}
|
}
|
||||||
return config_.height;
|
return config_.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* Application::getWindowTitle() const {
|
const char *Application::getWindowTitle() const {
|
||||||
return config_.title.c_str();
|
return config_.title.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,153 +1,29 @@
|
||||||
#include <module/module_registry.h>
|
#include <module/module_registry.h>
|
||||||
#include <event/event_bus.h>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
ModuleRegistry::ModuleRegistry() = default;
|
ModuleRegistry& ModuleRegistry::instance() {
|
||||||
|
static ModuleRegistry instance;
|
||||||
ModuleRegistry::~ModuleRegistry() {
|
return instance;
|
||||||
if (inited_) {
|
|
||||||
shutdownAll();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ModuleRegistry::ModuleRegistry(ModuleRegistry&&) noexcept = default;
|
std::vector<std::unique_ptr<Module>> ModuleRegistry::createModules() {
|
||||||
ModuleRegistry& ModuleRegistry::operator=(ModuleRegistry&&) noexcept = default;
|
// 按优先级排序(值小的先初始化)
|
||||||
|
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) {
|
std::vector<std::unique_ptr<Module>> modules;
|
||||||
if (!module) {
|
modules.reserve(sorted.size());
|
||||||
return;
|
|
||||||
|
for (const auto& info : sorted) {
|
||||||
|
modules.push_back(info.factory());
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* name = module->name();
|
return modules;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace extra2d
|
} // 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 <event/events.h>
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
@ -104,7 +104,9 @@ void InputModule::shutdown() {
|
||||||
endedTouches_.clear();
|
endedTouches_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputModule::update() {
|
void InputModule::update(float deltaTime) {
|
||||||
|
(void)deltaTime; // 未使用参数
|
||||||
|
|
||||||
std::memcpy(keyPrev_.data(), keyState_.data(), KEY_COUNT);
|
std::memcpy(keyPrev_.data(), keyState_.data(), KEY_COUNT);
|
||||||
std::memcpy(mousePrev_.data(), mouseState_.data(), static_cast<size_t>(MouseBtn::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 <platform/sdl2.h>
|
||||||
|
#include <config/app_config.h>
|
||||||
#include <event/events.h>
|
#include <event/events.h>
|
||||||
#include <utils/logger.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_DEPTH_SIZE, 24);
|
||||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
||||||
|
|
||||||
|
// 监听模块配置事件
|
||||||
|
events::OnModuleConfig<AppConfig>::subscribe(this, &WindowModule::onModuleConfig<AppConfig>);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -13,7 +13,7 @@ function define_extra2d_engine()
|
||||||
target("extra2d")
|
target("extra2d")
|
||||||
set_kind("static")
|
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_files("third_party/glad/src/glad.c")
|
||||||
|
|
||||||
add_includedirs("include", {public = true})
|
add_includedirs("include", {public = true})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue