refactor(引擎核心): 重构模块系统并引入自动注册机制

重构引擎核心模块系统,使用新的 Module 基类替代旧版 IModule 接口
新增模块自动注册机制,通过 E2D_REGISTER_MODULE 宏实现模块注册
将窗口、文件、定时器等模块迁移到新系统,支持配置事件驱动初始化
移除旧版 SDL2 封装和模块管理器,简化应用程序初始化流程
This commit is contained in:
ChestnutYueyue 2026-02-28 23:35:34 +08:00
parent ebf73a4492
commit f9be301dae
24 changed files with 1323 additions and 1418 deletions

View File

@ -1,8 +1,9 @@
#pragma once
#include <string>
#include <types/base/types.h>
#include <config/app_config.h>
#include <module/module.h>
#include <memory>
#include <vector>
namespace extra2d {
@ -12,27 +13,10 @@ class WindowModule;
class InputModule;
/**
* @brief
*/
struct AppConfig {
std::string title = "Extra2D Application";
int32 width = 1280;
int32 height = 720;
bool fullscreen = false;
bool resizable = true;
bool vsync = true;
int32 fpsLimit = 0;
int32 glMajor = 3;
int32 glMinor = 3;
bool enableCursors = true;
bool enableDpiScale = false;
};
/**
* @brief
* @brief -
*
*
* Context
*
*/
class Application {
public:
@ -102,7 +86,7 @@ public:
/**
* @brief FPS
*/
int32 fps() const { return currentFps_; }
int32 fps() const { return 0; } // TODO: 使用 SDL 计算 FPS
/**
* @brief
@ -117,12 +101,27 @@ public:
/**
* @brief
*/
WindowModule* getWindow() const { return windowModule_.get(); }
WindowModule* getWindow() const;
/**
* @brief
*/
InputModule* getInput() const { return inputModule_.get(); }
InputModule* getInput() const;
/**
* @brief
* @tparam T
* @return nullptr
*/
template<typename T>
T* getModule() const {
for (const auto& module : modules_) {
if (auto* ptr = dynamic_cast<T*>(module.get())) {
return ptr;
}
}
return nullptr;
}
/**
* @brief
@ -142,12 +141,11 @@ public:
private:
Application();
void mainLoop();
void update();
void initModules();
std::unique_ptr<Context> context_;
std::unique_ptr<WindowModule> windowModule_;
std::unique_ptr<InputModule> inputModule_;
std::vector<std::unique_ptr<Module>> modules_;
AppConfig config_;
@ -158,10 +156,6 @@ private:
float deltaTime_ = 0.0f;
float totalTime_ = 0.0f;
double lastFrameTime_ = 0.0;
int32 frameCount_ = 0;
float fpsTimer_ = 0.0f;
int32 currentFps_ = 0;
};
} // namespace extra2d

View File

@ -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

View File

@ -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

View File

@ -160,3 +160,31 @@
arg5, arg6, arg7); \
} \
};
/**
* @brief
* @param EventName
* @param BusName 线
*
* 使
* DECLARE_EVENT_T(OnModuleInit, Engine)
*
* // 发送事件
* OnModuleInit::emit<MyConfig>(myConfig);
*
* // 监听事件
* OnModuleInit::subscribe(this, &MyModule::onInit);
*/
#define DECLARE_EVENT_T(EventName, BusName) \
template <typename ConfigT> \
struct EventName \
: ::extra2d::event::EventTrait<E2D_EVENT_BUS_NAME_(BusName), \
const ConfigT &> { \
using Listener = ::extra2d::event::Listener<EventName<ConfigT>>; \
static constexpr const char *NAME = #EventName; \
static constexpr const char *BUS_NAME = \
E2D_EVENT_BUS_NAME_(BusName)::NAME; \
static void emit(const ConfigT &config) { \
::extra2d::event::broadcast<EventName<ConfigT>>(config); \
} \
};

View File

@ -3,7 +3,6 @@
#include <event/event_bus_macros.h>
#include <types/base/types.h>
namespace extra2d::events {
/**
@ -79,6 +78,19 @@ DECLARE_EVENT_0(OnFocus, Engine)
*/
DECLARE_EVENT_0(OnBlur, Engine)
/**
* @brief
*
* 使
* // 发送 AppConfig
* OnModuleConfig::emit(config);
*
* // 发送其他配置
* struct RenderConfig { int width, height; };
* OnModuleConfig::emit(renderConfig);
*/
DECLARE_EVENT_T(OnModuleConfig, Engine)
/**
* @brief
*/

View File

@ -40,6 +40,10 @@
#include <utils/logger.h>
#include <utils/random.h>
// Config
#include <config/app_config.h>
#include <config/window_config.h>
// Application
#include <app/application.h>

45
include/module/module.h Normal file
View File

@ -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

View File

@ -1,97 +1,111 @@
#pragma once
#include <module/imodule.h>
#include <functional>
#include <memory>
#include <module/module.h>
#include <string>
#include <unordered_map>
#include <typeindex>
#include <vector>
namespace extra2d {
/**
* @brief -
* @brief
*/
using ModuleFactory = std::function<std::unique_ptr<Module>()>;
/**
* @brief
*/
struct ModuleInfo {
std::string name;
ModuleFactory factory;
int32 priority;
std::type_index type;
};
/**
* @brief
*
* 线
*
*
* 使
*/
class ModuleRegistry {
public:
ModuleRegistry();
~ModuleRegistry();
// 禁止拷贝
ModuleRegistry(const ModuleRegistry &) = delete;
ModuleRegistry &operator=(const ModuleRegistry &) = delete;
// 允许移动
ModuleRegistry(ModuleRegistry &&) noexcept;
ModuleRegistry &operator=(ModuleRegistry &&) noexcept;
/**
* @brief
* @param module
* @brief
*/
void registerModule(IModule *module);
static ModuleRegistry &instance();
/**
* @brief
* @brief
* @tparam T
* @param name
* @param priority
*/
void unregisterModule(const char *name);
template <typename T>
void registerModule(const char *name, int32 priority = 100) {
static_assert(std::is_base_of_v<Module, T>, "T must inherit from Module");
ModuleInfo info{
name, []() -> std::unique_ptr<Module> { return std::make_unique<T>(); },
priority, std::type_index(typeid(T))};
registrations_.push_back(std::move(info));
}
/**
* @brief
* @param name
* @return nullptr
* @brief
* @return
*/
IModule *getModule(const char *name) const;
std::vector<std::unique_ptr<Module>> createModules();
/**
* @brief
* @param name
* @return
* @brief
*/
bool hasModule(const char *name) const;
/**
* @brief
* @return
*/
std::vector<IModule *> getAllModules() const;
/**
* @brief
* @param type
* @return
*/
std::vector<IModule *> getModulesByType(ModuleType type) const;
/**
* @brief
* @return
*/
bool initAll();
/**
* @brief
*/
void shutdownAll();
/**
* @brief
* @return
*/
size_t getModuleCount() const;
const std::vector<ModuleInfo> &getRegistrations() const {
return registrations_;
}
private:
/**
* @brief
*/
void sortModules();
std::vector<IModule *> modules_;
std::unordered_map<std::string, IModule *> moduleMap_;
bool sorted_ = false;
bool inited_ = false;
ModuleRegistry() = default;
std::vector<ModuleInfo> registrations_;
};
/**
* @brief
*/
template <typename T> class ModuleRegistrar {
public:
ModuleRegistrar(const char *name, int32 priority = 100) {
ModuleRegistry::instance().registerModule<T>(name, priority);
}
};
/**
* @brief
*
* 使
*
* class MyModule : public Module {
* E2D_REGISTER_MODULE(MyModule, "MyModule", 10)
* public:
* // ...
* };
*/
#define E2D_REGISTER_MODULE(ClassName, Name, Priority) \
private: \
static inline const extra2d::ModuleRegistrar<ClassName> _registrar{ \
Name, Priority}; \
\
public: \
const char *getName() const override { return Name; } \
int32 getPriority() const override { return Priority; }
/**
* @brief 使
*/
#define E2D_REGISTER_MODULE_SIMPLE(ClassName) \
E2D_REGISTER_MODULE(ClassName, #ClassName, 100)
} // namespace extra2d

View File

@ -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

View File

@ -1,8 +1,8 @@
#pragma once
#include <module/imodule.h>
#include <module/module.h>
#include <module/module_registry.h>
#include <types/base/types.h>
#include <types/const/priority.h>
#include <string>
#include <vector>
@ -37,7 +37,10 @@ struct FileData {
*
* Context
*/
class FileModule : public IModule {
class FileModule : public Module {
// 自动注册到模块系统,优先级为 20系统模块
E2D_REGISTER_MODULE(FileModule, "File", 20)
public:
FileModule();
~FileModule() override;
@ -50,10 +53,7 @@ public:
FileModule(FileModule&&) noexcept;
FileModule& operator=(FileModule&&) noexcept;
// IModule 接口实现
const char* name() const override { return "File"; }
ModuleType type() const override { return ModuleType::System; }
int priority() const override { return Pri::File; }
// Module 接口实现
bool init() override;
void shutdown() override;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,46 +1,22 @@
#include <app/application.h>
#include <context/context.h>
#include <event/events.h>
#include <platform/sdl2.h>
#include <platform/window.h>
#include <platform/input.h>
#include <module/module_registry.h>
#include <platform/input_module.h>
#include <platform/window_module.h>
#include <utils/logger.h>
#include <chrono>
#include <thread>
#ifdef __SWITCH__
#include <switch.h>
#endif
#include <SDL.h>
namespace extra2d {
/**
* @brief
*/
static double getTimeSeconds() {
#ifdef __SWITCH__
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return static_cast<double>(ts.tv_sec) +
static_cast<double>(ts.tv_nsec) / 1000000000.0;
#else
using namespace std::chrono;
auto now = steady_clock::now();
auto duration = now.time_since_epoch();
return duration_cast<std::chrono::duration<double>>(duration).count();
#endif
}
std::unique_ptr<Application> Application::create() {
return std::unique_ptr<Application>(new Application());
}
Application::Application() = default;
Application::~Application() {
shutdown();
}
Application::~Application() { shutdown(); }
Application::Application(Application &&) noexcept = default;
Application &Application::operator=(Application &&) noexcept = default;
@ -53,26 +29,6 @@ bool Application::init(const AppConfig &config) {
config_ = config;
#ifdef __SWITCH__
Result rc;
rc = romfsInit();
if (R_SUCCEEDED(rc)) {
E2D_LOG_INFO("RomFS initialized successfully");
} else {
E2D_LOG_WARN("romfsInit failed: {:#08X}", rc);
}
rc = socketInitializeDefault();
if (R_FAILED(rc)) {
E2D_LOG_WARN("socketInitializeDefault failed");
}
#endif
if (!Sdl2::initAll()) {
E2D_LOG_ERROR("Failed to initialize SDL2");
return false;
}
// 创建引擎上下文
context_ = Context::create();
if (!context_) {
@ -86,26 +42,11 @@ bool Application::init(const AppConfig &config) {
return false;
}
// 创建窗口模块
windowModule_ = std::make_unique<WindowModule>();
WindowCfg wcfg;
wcfg.title = config_.title;
wcfg.width = config_.width;
wcfg.height = config_.height;
wcfg.fullscreen = config_.fullscreen;
wcfg.resizable = config_.resizable;
wcfg.vsync = config_.vsync;
wcfg.glMajor = config_.glMajor;
wcfg.glMinor = config_.glMinor;
// 自动创建所有已注册的模块
initModules();
if (!windowModule_->create(wcfg)) {
E2D_LOG_ERROR("Failed to create window");
return false;
}
windowModule_->setVisible(true);
// 创建输入模块
inputModule_ = std::make_unique<InputModule>();
// 通过事件总线发送配置给所有监听模块
events::OnModuleConfig<AppConfig>::emit(config);
initialized_ = true;
running_ = true;
@ -113,11 +54,26 @@ bool Application::init(const AppConfig &config) {
events::OnInit::emit();
E2D_LOG_INFO("Application initialized successfully");
E2D_LOG_INFO("Window: {}x{}, Fullscreen: {}, VSync: {}", config_.width,
config_.height, config_.fullscreen, config_.vsync);
E2D_LOG_INFO("Window: {}x{}, Fullscreen: {}, VSync: {}", config.width,
config.height, config.fullscreen, config.vsync);
return true;
}
void Application::initModules() {
// 从注册表自动创建所有模块
modules_ = ModuleRegistry::instance().createModules();
// 初始化所有模块
for (auto &module : modules_) {
if (!module->init()) {
E2D_LOG_ERROR("Failed to initialize module: {}", module->getName());
} else {
E2D_LOG_INFO("Module initialized: {} (priority: {})", module->getName(),
module->getPriority());
}
}
}
void Application::shutdown() {
if (!initialized_)
return;
@ -126,20 +82,15 @@ void Application::shutdown() {
E2D_LOG_INFO("Shutting down application...");
// 智能指针自动销毁窗口和输入模块
inputModule_.reset();
windowModule_.reset();
// 按相反顺序销毁模块
for (auto it = modules_.rbegin(); it != modules_.rend(); ++it) {
(*it)->shutdown();
}
modules_.clear();
// 关闭上下文
context_.reset();
Sdl2::shutdown();
#ifdef __SWITCH__
romfsExit();
socketExit();
#endif
initialized_ = false;
running_ = false;
@ -152,10 +103,50 @@ void Application::run() {
return;
}
lastFrameTime_ = getTimeSeconds();
// 使用 SDL 的高精度计时器
Uint64 perfFreq = SDL_GetPerformanceFrequency();
Uint64 lastPerfCounter = SDL_GetPerformanceCounter();
WindowModule *window = getWindow();
InputModule *input = getInput();
while (running_) {
mainLoop();
// 处理窗口事件
if (window) {
if (!window->pollEvents()) {
quit();
break;
}
}
// 计算 deltaTime
Uint64 currentPerfCounter = SDL_GetPerformanceCounter();
deltaTime_ =
static_cast<float>(currentPerfCounter - lastPerfCounter) / perfFreq;
lastPerfCounter = currentPerfCounter;
totalTime_ += deltaTime_;
// 更新
if (!paused_) {
update();
}
// 交换缓冲区
if (window) {
window->swapBuffers();
}
// FPS 限制 - 使用 SDL_Delay
if (!config_.vsync && config_.fpsLimit > 0) {
Uint64 frameEndCounter = SDL_GetPerformanceCounter();
float frameTime =
static_cast<float>(frameEndCounter - currentPerfCounter) / perfFreq;
float targetTime = 1.0f / config_.fpsLimit;
if (frameTime < targetTime) {
SDL_Delay(static_cast<Uint32>((targetTime - frameTime) * 1000));
}
}
}
}
@ -176,74 +167,39 @@ void Application::resume() {
if (paused_) {
paused_ = false;
events::OnResume::emit();
lastFrameTime_ = getTimeSeconds();
E2D_LOG_INFO("Application resumed");
}
}
void Application::mainLoop() {
// 处理窗口事件
if (windowModule_) {
if (!windowModule_->pollEvents()) {
// 窗口关闭事件
quit();
return;
}
}
double currentTime = getTimeSeconds();
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
lastFrameTime_ = currentTime;
totalTime_ += deltaTime_;
frameCount_++;
fpsTimer_ += deltaTime_;
if (fpsTimer_ >= 1.0f) {
currentFps_ = frameCount_;
frameCount_ = 0;
fpsTimer_ -= 1.0f;
}
if (!paused_) {
update();
}
// 交换缓冲区
if (windowModule_) {
windowModule_->swapBuffers();
}
// 帧率限制
if (!config_.vsync && config_.fpsLimit > 0) {
double frameEndTime = getTimeSeconds();
double frameTime = frameEndTime - currentTime;
double target = 1.0 / static_cast<double>(config_.fpsLimit);
if (frameTime < target) {
std::this_thread::sleep_for(
std::chrono::duration<double>(target - frameTime));
}
}
}
void Application::update() {
// 更新所有模块
for (auto &module : modules_) {
module->update(deltaTime_);
}
// 通过上下文更新引擎
if (context_) {
context_->tick(deltaTime_);
}
}
WindowModule *Application::getWindow() const {
return getModule<WindowModule>();
}
InputModule *Application::getInput() const { return getModule<InputModule>(); }
int32 Application::getWindowWidth() const {
if (windowModule_) {
Size size = windowModule_->getSize();
if (WindowModule *window = getWindow()) {
Size size = window->getSize();
return static_cast<int32>(size.w);
}
return config_.width;
}
int32 Application::getWindowHeight() const {
if (windowModule_) {
Size size = windowModule_->getSize();
if (WindowModule *window = getWindow()) {
Size size = window->getSize();
return static_cast<int32>(size.h);
}
return config_.height;

View File

@ -1,153 +1,29 @@
#include <module/module_registry.h>
#include <event/event_bus.h>
#include <algorithm>
namespace extra2d {
ModuleRegistry::ModuleRegistry() = default;
ModuleRegistry::~ModuleRegistry() {
if (inited_) {
shutdownAll();
}
ModuleRegistry& ModuleRegistry::instance() {
static ModuleRegistry instance;
return instance;
}
ModuleRegistry::ModuleRegistry(ModuleRegistry&&) noexcept = default;
ModuleRegistry& ModuleRegistry::operator=(ModuleRegistry&&) noexcept = default;
std::vector<std::unique_ptr<Module>> ModuleRegistry::createModules() {
// 按优先级排序(值小的先初始化)
std::vector<ModuleInfo> sorted = registrations_;
std::sort(sorted.begin(), sorted.end(),
[](const ModuleInfo& a, const ModuleInfo& b) {
return a.priority < b.priority;
});
void ModuleRegistry::registerModule(IModule* module) {
if (!module) {
return;
std::vector<std::unique_ptr<Module>> modules;
modules.reserve(sorted.size());
for (const auto& info : sorted) {
modules.push_back(info.factory());
}
const char* name = module->name();
if (!name) {
return; // 名称为空
}
// 如果已存在同名模块,先注销旧的
if (moduleMap_.find(name) != moduleMap_.end()) {
unregisterModule(name);
}
modules_.push_back(module);
moduleMap_[name] = module;
sorted_ = false;
// 模块注册事件(暂不发送,避免依赖 events.h
// event::broadcast<events::OnModuleRegistered>(name, module->type());
}
void ModuleRegistry::unregisterModule(const char* name) {
if (!name) {
return;
}
auto it = moduleMap_.find(name);
if (it == moduleMap_.end()) {
return;
}
IModule* module = it->second;
// 如果已初始化,先关闭
if (inited_ && module) {
module->shutdown();
}
// 从列表中移除
modules_.erase(
std::remove(modules_.begin(), modules_.end(), module),
modules_.end()
);
moduleMap_.erase(it);
}
IModule* ModuleRegistry::getModule(const char* name) const {
if (!name) {
return nullptr;
}
auto it = moduleMap_.find(name);
return it != moduleMap_.end() ? it->second : nullptr;
}
bool ModuleRegistry::hasModule(const char* name) const {
if (!name) {
return false;
}
return moduleMap_.find(name) != moduleMap_.end();
}
std::vector<IModule*> ModuleRegistry::getAllModules() const {
return modules_;
}
std::vector<IModule*> ModuleRegistry::getModulesByType(ModuleType type) const {
std::vector<IModule*> result;
for (auto* module : modules_) {
if (module && module->type() == type) {
result.push_back(module);
}
}
return result;
}
bool ModuleRegistry::initAll() {
if (inited_) {
return true;
}
// 按优先级排序
sortModules();
// 初始化所有模块
for (auto* module : modules_) {
if (module && !module->init()) {
// 初始化失败,关闭已初始化的模块
shutdownAll();
return false;
}
}
inited_ = true;
return true;
}
void ModuleRegistry::shutdownAll() {
if (!inited_) {
return;
}
// 按优先级逆序关闭
for (auto it = modules_.rbegin(); it != modules_.rend(); ++it) {
if (*it) {
(*it)->shutdown();
}
}
inited_ = false;
}
size_t ModuleRegistry::getModuleCount() const {
return modules_.size();
}
void ModuleRegistry::sortModules() {
if (sorted_) {
return;
}
// 按优先级排序(数值小的优先)
std::sort(modules_.begin(), modules_.end(),
[](IModule* a, IModule* b) {
if (!a || !b) return a < b;
return a->priority() < b->priority();
}
);
sorted_ = true;
return modules;
}
} // namespace extra2d

View File

@ -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

View File

@ -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

View File

@ -1,4 +1,4 @@
#include <platform/input.h>
#include <platform/input_module.h>
#include <event/events.h>
#include <SDL.h>
#include <cstring>
@ -104,7 +104,9 @@ void InputModule::shutdown() {
endedTouches_.clear();
}
void InputModule::update() {
void InputModule::update(float deltaTime) {
(void)deltaTime; // 未使用参数
std::memcpy(keyPrev_.data(), keyState_.data(), KEY_COUNT);
std::memcpy(mousePrev_.data(), mouseState_.data(), static_cast<size_t>(MouseBtn::Count));

View File

@ -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

View File

@ -1,5 +1,6 @@
#include <platform/window.h>
#include <platform/window_module.h>
#include <platform/sdl2.h>
#include <config/app_config.h>
#include <event/events.h>
#include <utils/logger.h>
@ -49,6 +50,9 @@ bool WindowModule::init() {
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
// 监听模块配置事件
events::OnModuleConfig<AppConfig>::subscribe(this, &WindowModule::onModuleConfig<AppConfig>);
return true;
}

View File

@ -13,7 +13,7 @@ function define_extra2d_engine()
target("extra2d")
set_kind("static")
add_files("src/**.cpp|core/*.cpp|module/module_manager.cpp|plugin/plugin_manager.cpp")
add_files("src/**.cpp|core/*.cpp|module/module_manager.cpp|plugin/plugin_manager.cpp|platform/window.cpp|platform/input.cpp")
add_files("third_party/glad/src/glad.c")
add_includedirs("include", {public = true})