refactor(module): 重构模块系统并添加核心模块实现
重构模块注册表结构以支持初始化器实例缓存,添加配置、日志、平台和渲染等核心模块实现 优化日志系统移除SDL依赖,改进跨平台支持 调整模块初始化流程,增强模块间依赖管理
This commit is contained in:
parent
38148a6c54
commit
8f7f1612eb
|
|
@ -4,8 +4,6 @@
|
||||||
#include <extra2d/config/app_config.h>
|
#include <extra2d/config/app_config.h>
|
||||||
#include <extra2d/config/config_manager.h>
|
#include <extra2d/config/config_manager.h>
|
||||||
#include <extra2d/config/module_config.h>
|
#include <extra2d/config/module_config.h>
|
||||||
#include <extra2d/config/platform_config.h>
|
|
||||||
#include <extra2d/graphics/render_backend.h>
|
|
||||||
#include <extra2d/platform/iwindow.h>
|
#include <extra2d/platform/iwindow.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
|
@ -18,198 +16,58 @@ class EventQueue;
|
||||||
class EventDispatcher;
|
class EventDispatcher;
|
||||||
class Camera;
|
class Camera;
|
||||||
class ViewportAdapter;
|
class ViewportAdapter;
|
||||||
|
class RenderBackend;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Application 单例 - 应用主控
|
|
||||||
*
|
|
||||||
* 负责管理应用程序的整个生命周期,包括初始化、主循环、渲染和关闭。
|
|
||||||
* 集成了 ConfigManager 和 ModuleRegistry 系统进行配置和模块管理。
|
|
||||||
*/
|
|
||||||
class Application {
|
class Application {
|
||||||
public:
|
public:
|
||||||
/**
|
|
||||||
* @brief 获取单例实例
|
|
||||||
* @return Application 实例引用
|
|
||||||
*/
|
|
||||||
static Application& get();
|
static Application& get();
|
||||||
|
|
||||||
Application(const Application&) = delete;
|
Application(const Application&) = delete;
|
||||||
Application& operator=(const Application&) = delete;
|
Application& operator=(const Application&) = delete;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 使用默认配置初始化
|
|
||||||
* @return 初始化成功返回 true
|
|
||||||
*/
|
|
||||||
bool init();
|
bool init();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 使用配置结构初始化
|
|
||||||
* @param config 应用配置
|
|
||||||
* @return 初始化成功返回 true
|
|
||||||
*/
|
|
||||||
bool init(const AppConfig& config);
|
bool init(const AppConfig& config);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从配置文件初始化
|
|
||||||
* @param configPath 配置文件路径(支持 .json 和 .ini)
|
|
||||||
* @return 初始化成功返回 true
|
|
||||||
*/
|
|
||||||
bool init(const std::string& configPath);
|
bool init(const std::string& configPath);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭应用程序
|
|
||||||
*/
|
|
||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 运行主循环
|
|
||||||
*/
|
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 退出应用程序
|
|
||||||
*/
|
|
||||||
void quit();
|
void quit();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 暂停应用程序
|
|
||||||
*/
|
|
||||||
void pause();
|
void pause();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 恢复应用程序
|
|
||||||
*/
|
|
||||||
void resume();
|
void resume();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否暂停
|
|
||||||
* @return 暂停状态返回 true
|
|
||||||
*/
|
|
||||||
bool isPaused() const { return paused_; }
|
bool isPaused() const { return paused_; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否运行中
|
|
||||||
* @return 运行中返回 true
|
|
||||||
*/
|
|
||||||
bool isRunning() const { return running_; }
|
bool isRunning() const { return running_; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取窗口接口
|
|
||||||
* @return 窗口接口引用
|
|
||||||
*/
|
|
||||||
IWindow& window() { return *window_; }
|
IWindow& window() { return *window_; }
|
||||||
|
RenderBackend& renderer();
|
||||||
/**
|
|
||||||
* @brief 获取渲染后端
|
|
||||||
* @return 渲染后端引用
|
|
||||||
*/
|
|
||||||
RenderBackend& renderer() { return *renderer_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取输入接口
|
|
||||||
* @return 输入接口引用
|
|
||||||
*/
|
|
||||||
IInput& input();
|
IInput& input();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取场景管理器
|
|
||||||
* @return 场景管理器引用
|
|
||||||
*/
|
|
||||||
SceneManager& scenes();
|
SceneManager& scenes();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取定时器管理器
|
|
||||||
* @return 定时器管理器引用
|
|
||||||
*/
|
|
||||||
TimerManager& timers();
|
TimerManager& timers();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取事件队列
|
|
||||||
* @return 事件队列引用
|
|
||||||
*/
|
|
||||||
EventQueue& eventQueue();
|
EventQueue& eventQueue();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取事件分发器
|
|
||||||
* @return 事件分发器引用
|
|
||||||
*/
|
|
||||||
EventDispatcher& eventDispatcher();
|
EventDispatcher& eventDispatcher();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取相机
|
|
||||||
* @return 相机引用
|
|
||||||
*/
|
|
||||||
Camera& camera();
|
Camera& camera();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取视口适配器
|
|
||||||
* @return 视口适配器引用
|
|
||||||
*/
|
|
||||||
ViewportAdapter& viewportAdapter();
|
ViewportAdapter& viewportAdapter();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 进入场景
|
|
||||||
* @param scene 场景指针
|
|
||||||
*/
|
|
||||||
void enterScene(Ptr<class Scene> scene);
|
void enterScene(Ptr<class Scene> scene);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取帧时间
|
|
||||||
* @return 帧时间(秒)
|
|
||||||
*/
|
|
||||||
float deltaTime() const { return deltaTime_; }
|
float deltaTime() const { return deltaTime_; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取总运行时间
|
|
||||||
* @return 总运行时间(秒)
|
|
||||||
*/
|
|
||||||
float totalTime() const { return totalTime_; }
|
float totalTime() const { return totalTime_; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前帧率
|
|
||||||
* @return 帧率(FPS)
|
|
||||||
*/
|
|
||||||
int fps() const { return currentFps_; }
|
int fps() const { return currentFps_; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取配置管理器
|
|
||||||
* @return 配置管理器引用
|
|
||||||
*/
|
|
||||||
ConfigManager& config();
|
ConfigManager& config();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取应用配置
|
|
||||||
* @return 应用配置常量引用
|
|
||||||
*/
|
|
||||||
const AppConfig& getConfig() const;
|
const AppConfig& getConfig() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Application() = default;
|
Application() = default;
|
||||||
~Application();
|
~Application();
|
||||||
|
|
||||||
/**
|
bool initModules();
|
||||||
* @brief 内部初始化实现
|
|
||||||
* @return 初始化成功返回 true
|
|
||||||
*/
|
|
||||||
bool initImpl();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 主循环
|
|
||||||
*/
|
|
||||||
void mainLoop();
|
void mainLoop();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新逻辑
|
|
||||||
*/
|
|
||||||
void update();
|
void update();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 渲染
|
|
||||||
*/
|
|
||||||
void render();
|
void render();
|
||||||
|
|
||||||
UniquePtr<IWindow> window_;
|
IWindow* window_ = nullptr;
|
||||||
UniquePtr<RenderBackend> renderer_;
|
|
||||||
UniquePtr<SceneManager> sceneManager_;
|
UniquePtr<SceneManager> sceneManager_;
|
||||||
UniquePtr<TimerManager> timerManager_;
|
UniquePtr<TimerManager> timerManager_;
|
||||||
UniquePtr<EventQueue> eventQueue_;
|
UniquePtr<EventQueue> eventQueue_;
|
||||||
|
|
@ -228,10 +86,6 @@ private:
|
||||||
int frameCount_ = 0;
|
int frameCount_ = 0;
|
||||||
float fpsTimer_ = 0.0f;
|
float fpsTimer_ = 0.0f;
|
||||||
int currentFps_ = 0;
|
int currentFps_ = 0;
|
||||||
|
|
||||||
ModuleId windowModuleId_ = INVALID_MODULE_ID;
|
|
||||||
ModuleId inputModuleId_ = INVALID_MODULE_ID;
|
|
||||||
ModuleId renderModuleId_ = INVALID_MODULE_ID;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/config/module_config.h>
|
||||||
|
#include <extra2d/config/module_initializer.h>
|
||||||
|
#include <extra2d/config/app_config.h>
|
||||||
|
#include <extra2d/config/config_manager.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
class ConfigModuleConfig : public IModuleConfig {
|
||||||
|
public:
|
||||||
|
std::string configPath;
|
||||||
|
AppConfig appConfig;
|
||||||
|
|
||||||
|
ModuleInfo getModuleInfo() const override {
|
||||||
|
ModuleInfo info;
|
||||||
|
info.id = 0;
|
||||||
|
info.name = "Config";
|
||||||
|
info.version = "1.0.0";
|
||||||
|
info.priority = ModulePriority::Core;
|
||||||
|
info.enabled = true;
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getConfigSectionName() const override {
|
||||||
|
return "config";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validate() const override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetToDefaults() override {
|
||||||
|
configPath.clear();
|
||||||
|
appConfig = AppConfig{};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loadFromJson(const void* jsonData) override;
|
||||||
|
bool saveToJson(void* jsonData) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ConfigModuleInitializer : public IModuleInitializer {
|
||||||
|
public:
|
||||||
|
ConfigModuleInitializer();
|
||||||
|
~ConfigModuleInitializer() override;
|
||||||
|
|
||||||
|
ModuleId getModuleId() const override { return moduleId_; }
|
||||||
|
ModulePriority getPriority() const override { return ModulePriority::Core; }
|
||||||
|
std::vector<ModuleId> getDependencies() const override { return {}; }
|
||||||
|
|
||||||
|
bool initialize(const IModuleConfig* config) override;
|
||||||
|
void shutdown() override;
|
||||||
|
bool isInitialized() const override { return initialized_; }
|
||||||
|
|
||||||
|
void setModuleId(ModuleId id) { moduleId_ = id; }
|
||||||
|
void setAppConfig(const AppConfig& config) { appConfig_ = config; }
|
||||||
|
void setConfigPath(const std::string& path) { configPath_ = path; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
ModuleId moduleId_ = INVALID_MODULE_ID;
|
||||||
|
bool initialized_ = false;
|
||||||
|
AppConfig appConfig_;
|
||||||
|
std::string configPath_;
|
||||||
|
};
|
||||||
|
|
||||||
|
ModuleId get_config_module_id();
|
||||||
|
void register_config_module();
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -10,12 +10,13 @@ namespace extra2d {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 模块注册表条目结构体
|
* @brief 模块注册表条目结构体
|
||||||
* 存储模块的配置和初始化器工厂
|
* 存储模块的配置和初始化器
|
||||||
*/
|
*/
|
||||||
struct ModuleEntry {
|
struct ModuleEntry {
|
||||||
ModuleId id; ///< 模块标识符
|
ModuleId id; ///< 模块标识符
|
||||||
UniquePtr<IModuleConfig> config; ///< 模块配置
|
UniquePtr<IModuleConfig> config; ///< 模块配置
|
||||||
ModuleInitializerFactory initializerFactory;///< 初始化器工厂函数
|
ModuleInitializerFactory initializerFactory;///< 初始化器工厂函数
|
||||||
|
UniquePtr<IModuleInitializer> initializer; ///< 初始化器实例
|
||||||
bool initialized = false; ///< 是否已初始化
|
bool initialized = false; ///< 是否已初始化
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -74,11 +75,11 @@ public:
|
||||||
IModuleConfig* getModuleConfigByName(const std::string& name) const;
|
IModuleConfig* getModuleConfigByName(const std::string& name) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 创建模块初始化器
|
* @brief 获取或创建模块初始化器
|
||||||
* @param id 模块标识符
|
* @param id 模块标识符
|
||||||
* @return 初始化器实例,不存在返回 nullptr
|
* @return 初始化器指针,不存在返回 nullptr
|
||||||
*/
|
*/
|
||||||
UniquePtr<IModuleInitializer> createInitializer(ModuleId id) const;
|
IModuleInitializer* getInitializer(ModuleId id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取所有已注册模块标识符
|
* @brief 获取所有已注册模块标识符
|
||||||
|
|
|
||||||
|
|
@ -1,212 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
// SDL2 键码定义
|
|
||||||
#include <SDL2/SDL.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 键盘按键码 (基于 SDL2)
|
|
||||||
// ============================================================================
|
|
||||||
namespace Key {
|
|
||||||
enum : int {
|
|
||||||
Unknown = SDLK_UNKNOWN,
|
|
||||||
Space = SDLK_SPACE,
|
|
||||||
Apostrophe = SDLK_QUOTE,
|
|
||||||
Comma = SDLK_COMMA,
|
|
||||||
Minus = SDLK_MINUS,
|
|
||||||
Period = SDLK_PERIOD,
|
|
||||||
Slash = SDLK_SLASH,
|
|
||||||
Num0 = SDLK_0,
|
|
||||||
Num1 = SDLK_1,
|
|
||||||
Num2 = SDLK_2,
|
|
||||||
Num3 = SDLK_3,
|
|
||||||
Num4 = SDLK_4,
|
|
||||||
Num5 = SDLK_5,
|
|
||||||
Num6 = SDLK_6,
|
|
||||||
Num7 = SDLK_7,
|
|
||||||
Num8 = SDLK_8,
|
|
||||||
Num9 = SDLK_9,
|
|
||||||
Semicolon = SDLK_SEMICOLON,
|
|
||||||
Equal = SDLK_EQUALS,
|
|
||||||
A = SDLK_a,
|
|
||||||
B = SDLK_b,
|
|
||||||
C = SDLK_c,
|
|
||||||
D = SDLK_d,
|
|
||||||
E = SDLK_e,
|
|
||||||
F = SDLK_f,
|
|
||||||
G = SDLK_g,
|
|
||||||
H = SDLK_h,
|
|
||||||
I = SDLK_i,
|
|
||||||
J = SDLK_j,
|
|
||||||
K = SDLK_k,
|
|
||||||
L = SDLK_l,
|
|
||||||
M = SDLK_m,
|
|
||||||
N = SDLK_n,
|
|
||||||
O = SDLK_o,
|
|
||||||
P = SDLK_p,
|
|
||||||
Q = SDLK_q,
|
|
||||||
R = SDLK_r,
|
|
||||||
S = SDLK_s,
|
|
||||||
T = SDLK_t,
|
|
||||||
U = SDLK_u,
|
|
||||||
V = SDLK_v,
|
|
||||||
W = SDLK_w,
|
|
||||||
X = SDLK_x,
|
|
||||||
Y = SDLK_y,
|
|
||||||
Z = SDLK_z,
|
|
||||||
LeftBracket = SDLK_LEFTBRACKET,
|
|
||||||
Backslash = SDLK_BACKSLASH,
|
|
||||||
RightBracket = SDLK_RIGHTBRACKET,
|
|
||||||
GraveAccent = SDLK_BACKQUOTE,
|
|
||||||
Escape = SDLK_ESCAPE,
|
|
||||||
Enter = SDLK_RETURN,
|
|
||||||
Tab = SDLK_TAB,
|
|
||||||
Backspace = SDLK_BACKSPACE,
|
|
||||||
Insert = SDLK_INSERT,
|
|
||||||
Delete = SDLK_DELETE,
|
|
||||||
Right = SDLK_RIGHT,
|
|
||||||
Left = SDLK_LEFT,
|
|
||||||
Down = SDLK_DOWN,
|
|
||||||
Up = SDLK_UP,
|
|
||||||
PageUp = SDLK_PAGEUP,
|
|
||||||
PageDown = SDLK_PAGEDOWN,
|
|
||||||
Home = SDLK_HOME,
|
|
||||||
End = SDLK_END,
|
|
||||||
CapsLock = SDLK_CAPSLOCK,
|
|
||||||
ScrollLock = SDLK_SCROLLLOCK,
|
|
||||||
NumLock = SDLK_NUMLOCKCLEAR,
|
|
||||||
PrintScreen = SDLK_PRINTSCREEN,
|
|
||||||
Pause = SDLK_PAUSE,
|
|
||||||
F1 = SDLK_F1,
|
|
||||||
F2 = SDLK_F2,
|
|
||||||
F3 = SDLK_F3,
|
|
||||||
F4 = SDLK_F4,
|
|
||||||
F5 = SDLK_F5,
|
|
||||||
F6 = SDLK_F6,
|
|
||||||
F7 = SDLK_F7,
|
|
||||||
F8 = SDLK_F8,
|
|
||||||
F9 = SDLK_F9,
|
|
||||||
F10 = SDLK_F10,
|
|
||||||
F11 = SDLK_F11,
|
|
||||||
F12 = SDLK_F12,
|
|
||||||
F13 = SDLK_F13,
|
|
||||||
F14 = SDLK_F14,
|
|
||||||
F15 = SDLK_F15,
|
|
||||||
F16 = SDLK_F16,
|
|
||||||
F17 = SDLK_F17,
|
|
||||||
F18 = SDLK_F18,
|
|
||||||
F19 = SDLK_F19,
|
|
||||||
F20 = SDLK_F20,
|
|
||||||
F21 = SDLK_F21,
|
|
||||||
F22 = SDLK_F22,
|
|
||||||
F23 = SDLK_F23,
|
|
||||||
F24 = SDLK_F24,
|
|
||||||
KP0 = SDLK_KP_0,
|
|
||||||
KP1 = SDLK_KP_1,
|
|
||||||
KP2 = SDLK_KP_2,
|
|
||||||
KP3 = SDLK_KP_3,
|
|
||||||
KP4 = SDLK_KP_4,
|
|
||||||
KP5 = SDLK_KP_5,
|
|
||||||
KP6 = SDLK_KP_6,
|
|
||||||
KP7 = SDLK_KP_7,
|
|
||||||
KP8 = SDLK_KP_8,
|
|
||||||
KP9 = SDLK_KP_9,
|
|
||||||
KPDecimal = SDLK_KP_PERIOD,
|
|
||||||
KPDivide = SDLK_KP_DIVIDE,
|
|
||||||
KPMultiply = SDLK_KP_MULTIPLY,
|
|
||||||
KPSubtract = SDLK_KP_MINUS,
|
|
||||||
KPAdd = SDLK_KP_PLUS,
|
|
||||||
KPEnter = SDLK_KP_ENTER,
|
|
||||||
KPEqual = SDLK_KP_EQUALS,
|
|
||||||
LeftShift = SDLK_LSHIFT,
|
|
||||||
LeftControl = SDLK_LCTRL,
|
|
||||||
LeftAlt = SDLK_LALT,
|
|
||||||
LeftSuper = SDLK_LGUI,
|
|
||||||
RightShift = SDLK_RSHIFT,
|
|
||||||
RightControl = SDLK_RCTRL,
|
|
||||||
RightAlt = SDLK_RALT,
|
|
||||||
RightSuper = SDLK_RGUI,
|
|
||||||
Menu = SDLK_MENU,
|
|
||||||
Last = SDLK_MENU
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 修饰键
|
|
||||||
// ============================================================================
|
|
||||||
namespace Mod {
|
|
||||||
enum : int {
|
|
||||||
Shift = KMOD_SHIFT,
|
|
||||||
Control = KMOD_CTRL,
|
|
||||||
Alt = KMOD_ALT,
|
|
||||||
Super = KMOD_GUI,
|
|
||||||
CapsLock = KMOD_CAPS,
|
|
||||||
NumLock = KMOD_NUM
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 鼠标按键码
|
|
||||||
// ============================================================================
|
|
||||||
namespace Mouse {
|
|
||||||
enum : int {
|
|
||||||
Button1 = 0,
|
|
||||||
Button2 = 1,
|
|
||||||
Button3 = 2,
|
|
||||||
Button4 = 3,
|
|
||||||
Button5 = 4,
|
|
||||||
Button6 = 5,
|
|
||||||
Button7 = 6,
|
|
||||||
Button8 = 7,
|
|
||||||
ButtonLast = Button8,
|
|
||||||
ButtonLeft = Button1,
|
|
||||||
ButtonRight = Button2,
|
|
||||||
ButtonMiddle = Button3
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 游戏手柄按键
|
|
||||||
// ============================================================================
|
|
||||||
namespace GamepadButton {
|
|
||||||
enum : int {
|
|
||||||
A = SDL_CONTROLLER_BUTTON_A,
|
|
||||||
B = SDL_CONTROLLER_BUTTON_B,
|
|
||||||
X = SDL_CONTROLLER_BUTTON_X,
|
|
||||||
Y = SDL_CONTROLLER_BUTTON_Y,
|
|
||||||
LeftBumper = SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
|
|
||||||
RightBumper = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
|
|
||||||
Back = SDL_CONTROLLER_BUTTON_BACK,
|
|
||||||
Start = SDL_CONTROLLER_BUTTON_START,
|
|
||||||
Guide = SDL_CONTROLLER_BUTTON_GUIDE,
|
|
||||||
LeftThumb = SDL_CONTROLLER_BUTTON_LEFTSTICK,
|
|
||||||
RightThumb = SDL_CONTROLLER_BUTTON_RIGHTSTICK,
|
|
||||||
DPadUp = SDL_CONTROLLER_BUTTON_DPAD_UP,
|
|
||||||
DPadRight = SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
|
|
||||||
DPadDown = SDL_CONTROLLER_BUTTON_DPAD_DOWN,
|
|
||||||
DPadLeft = SDL_CONTROLLER_BUTTON_DPAD_LEFT,
|
|
||||||
Last = SDL_CONTROLLER_BUTTON_DPAD_LEFT,
|
|
||||||
Cross = A,
|
|
||||||
Circle = B,
|
|
||||||
Square = X,
|
|
||||||
Triangle = Y
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 游戏手柄轴
|
|
||||||
// ============================================================================
|
|
||||||
namespace GamepadAxis {
|
|
||||||
enum : int {
|
|
||||||
LeftX = SDL_CONTROLLER_AXIS_LEFTX,
|
|
||||||
LeftY = SDL_CONTROLLER_AXIS_LEFTY,
|
|
||||||
RightX = SDL_CONTROLLER_AXIS_RIGHTX,
|
|
||||||
RightY = SDL_CONTROLLER_AXIS_RIGHTY,
|
|
||||||
LeftTrigger = SDL_CONTROLLER_AXIS_TRIGGERLEFT,
|
|
||||||
RightTrigger = SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
|
|
||||||
Last = SDL_CONTROLLER_AXIS_TRIGGERRIGHT
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -7,10 +7,6 @@
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 渲染模块配置
|
|
||||||
* 实现 IModuleConfig 接口
|
|
||||||
*/
|
|
||||||
class RenderModuleConfig : public IModuleConfig {
|
class RenderModuleConfig : public IModuleConfig {
|
||||||
public:
|
public:
|
||||||
BackendType backend = BackendType::OpenGL;
|
BackendType backend = BackendType::OpenGL;
|
||||||
|
|
@ -20,10 +16,6 @@ public:
|
||||||
bool sRGBFramebuffer = false;
|
bool sRGBFramebuffer = false;
|
||||||
int spriteBatchSize = 1000;
|
int spriteBatchSize = 1000;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取模块信息
|
|
||||||
* @return 模块信息结构体
|
|
||||||
*/
|
|
||||||
ModuleInfo getModuleInfo() const override {
|
ModuleInfo getModuleInfo() const override {
|
||||||
ModuleInfo info;
|
ModuleInfo info;
|
||||||
info.name = "Render";
|
info.name = "Render";
|
||||||
|
|
@ -33,115 +25,41 @@ public:
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取配置节名称
|
|
||||||
* @return 配置节名称字符串
|
|
||||||
*/
|
|
||||||
std::string getConfigSectionName() const override { return "render"; }
|
std::string getConfigSectionName() const override { return "render"; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 验证配置有效性
|
|
||||||
* @return 如果配置有效返回 true
|
|
||||||
*/
|
|
||||||
bool validate() const override;
|
bool validate() const override;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 应用平台约束
|
|
||||||
* 根据平台特性调整配置
|
|
||||||
* @param platform 目标平台类型
|
|
||||||
*/
|
|
||||||
void applyPlatformConstraints(PlatformType platform) override;
|
void applyPlatformConstraints(PlatformType platform) override;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 重置为默认配置
|
|
||||||
*/
|
|
||||||
void resetToDefaults() override;
|
void resetToDefaults() override;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从 JSON 数据加载配置
|
|
||||||
* @param jsonData JSON 数据指针
|
|
||||||
* @return 加载成功返回 true
|
|
||||||
*/
|
|
||||||
bool loadFromJson(const void* jsonData) override;
|
bool loadFromJson(const void* jsonData) override;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 保存配置到 JSON 数据
|
|
||||||
* @param jsonData JSON 数据指针
|
|
||||||
* @return 保存成功返回 true
|
|
||||||
*/
|
|
||||||
bool saveToJson(void* jsonData) const override;
|
bool saveToJson(void* jsonData) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 渲染模块初始化器
|
|
||||||
* 实现 IModuleInitializer 接口
|
|
||||||
* 依赖窗口模块
|
|
||||||
*/
|
|
||||||
class RenderModuleInitializer : public IModuleInitializer {
|
class RenderModuleInitializer : public IModuleInitializer {
|
||||||
public:
|
public:
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
*/
|
|
||||||
RenderModuleInitializer();
|
RenderModuleInitializer();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*/
|
|
||||||
~RenderModuleInitializer() override;
|
~RenderModuleInitializer() override;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取模块标识符
|
|
||||||
* @return 模块唯一标识符
|
|
||||||
*/
|
|
||||||
ModuleId getModuleId() const override { return moduleId_; }
|
ModuleId getModuleId() const override { return moduleId_; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取模块优先级
|
|
||||||
* @return 模块优先级
|
|
||||||
*/
|
|
||||||
ModulePriority getPriority() const override { return ModulePriority::Graphics; }
|
ModulePriority getPriority() const override { return ModulePriority::Graphics; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取模块依赖列表
|
|
||||||
* @return 依赖模块标识符列表
|
|
||||||
*/
|
|
||||||
std::vector<ModuleId> getDependencies() const override;
|
std::vector<ModuleId> getDependencies() const override;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化模块
|
|
||||||
* @param config 模块配置指针
|
|
||||||
* @return 初始化成功返回 true
|
|
||||||
*/
|
|
||||||
bool initialize(const IModuleConfig* config) override;
|
bool initialize(const IModuleConfig* config) override;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭模块
|
|
||||||
*/
|
|
||||||
void shutdown() override;
|
void shutdown() override;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查模块是否已初始化
|
|
||||||
* @return 已初始化返回 true
|
|
||||||
*/
|
|
||||||
bool isInitialized() const override { return initialized_; }
|
bool isInitialized() const override { return initialized_; }
|
||||||
|
|
||||||
/**
|
void setModuleId(ModuleId id) { moduleId_ = id; }
|
||||||
* @brief 获取渲染器实例
|
void setWindow(IWindow* window) { window_ = window; }
|
||||||
* @return 渲染后端指针
|
|
||||||
*/
|
|
||||||
RenderBackend* getRenderer() const { return renderer_.get(); }
|
RenderBackend* getRenderer() const { return renderer_.get(); }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置窗口模块标识符
|
|
||||||
* @param windowModuleId 窗口模块标识符
|
|
||||||
*/
|
|
||||||
void setWindowModuleId(ModuleId windowModuleId) { windowModuleId_ = windowModuleId; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ModuleId moduleId_ = INVALID_MODULE_ID;
|
ModuleId moduleId_ = INVALID_MODULE_ID;
|
||||||
ModuleId windowModuleId_ = INVALID_MODULE_ID;
|
IWindow* window_ = nullptr;
|
||||||
UniquePtr<RenderBackend> renderer_;
|
UniquePtr<RenderBackend> renderer_;
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ModuleId get_render_module_id();
|
||||||
|
void register_render_module();
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/config/module_config.h>
|
||||||
|
#include <extra2d/config/module_initializer.h>
|
||||||
|
#include <extra2d/config/platform_config.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
class PlatformModuleConfig : public IModuleConfig {
|
||||||
|
public:
|
||||||
|
PlatformType targetPlatform = PlatformType::Auto;
|
||||||
|
|
||||||
|
ModuleInfo getModuleInfo() const override {
|
||||||
|
ModuleInfo info;
|
||||||
|
info.id = 0;
|
||||||
|
info.name = "Platform";
|
||||||
|
info.version = "1.0.0";
|
||||||
|
info.priority = ModulePriority::Core;
|
||||||
|
info.enabled = true;
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getConfigSectionName() const override {
|
||||||
|
return "platform";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validate() const override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetToDefaults() override {
|
||||||
|
targetPlatform = PlatformType::Auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loadFromJson(const void* jsonData) override;
|
||||||
|
bool saveToJson(void* jsonData) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PlatformModuleInitializer : public IModuleInitializer {
|
||||||
|
public:
|
||||||
|
PlatformModuleInitializer();
|
||||||
|
~PlatformModuleInitializer() override;
|
||||||
|
|
||||||
|
ModuleId getModuleId() const override { return moduleId_; }
|
||||||
|
ModulePriority getPriority() const override { return ModulePriority::Core; }
|
||||||
|
std::vector<ModuleId> getDependencies() const override { return {}; }
|
||||||
|
|
||||||
|
bool initialize(const IModuleConfig* config) override;
|
||||||
|
void shutdown() override;
|
||||||
|
bool isInitialized() const override { return initialized_; }
|
||||||
|
|
||||||
|
void setModuleId(ModuleId id) { moduleId_ = id; }
|
||||||
|
void setPlatform(PlatformType platform) { targetPlatform_ = platform; }
|
||||||
|
|
||||||
|
PlatformType getPlatform() const { return resolvedPlatform_; }
|
||||||
|
PlatformConfig* getPlatformConfig() const { return platformConfig_.get(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool initSwitch();
|
||||||
|
void shutdownSwitch();
|
||||||
|
|
||||||
|
ModuleId moduleId_ = INVALID_MODULE_ID;
|
||||||
|
bool initialized_ = false;
|
||||||
|
PlatformType targetPlatform_ = PlatformType::Auto;
|
||||||
|
PlatformType resolvedPlatform_ = PlatformType::Windows;
|
||||||
|
UniquePtr<PlatformConfig> platformConfig_;
|
||||||
|
};
|
||||||
|
|
||||||
|
ModuleId get_platform_module_id();
|
||||||
|
void register_platform_module();
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -4,133 +4,74 @@
|
||||||
#include <extra2d/config/module_initializer.h>
|
#include <extra2d/config/module_initializer.h>
|
||||||
#include <extra2d/config/app_config.h>
|
#include <extra2d/config/app_config.h>
|
||||||
#include <extra2d/platform/iwindow.h>
|
#include <extra2d/platform/iwindow.h>
|
||||||
#include <extra2d/core/types.h>
|
#include <string>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 窗口模块配置
|
|
||||||
* 实现 IModuleConfig 接口
|
|
||||||
*/
|
|
||||||
class WindowModuleConfig : public IModuleConfig {
|
class WindowModuleConfig : public IModuleConfig {
|
||||||
public:
|
public:
|
||||||
WindowConfigData windowConfig;
|
|
||||||
std::string backend = "sdl2";
|
std::string backend = "sdl2";
|
||||||
|
WindowConfigData windowConfig;
|
||||||
/**
|
|
||||||
* @brief 获取模块信息
|
|
||||||
* @return 模块信息结构体
|
|
||||||
*/
|
|
||||||
ModuleInfo getModuleInfo() const override {
|
ModuleInfo getModuleInfo() const override {
|
||||||
ModuleInfo info;
|
ModuleInfo info;
|
||||||
|
info.id = 0;
|
||||||
info.name = "Window";
|
info.name = "Window";
|
||||||
info.version = "1.0.0";
|
info.version = "1.0.0";
|
||||||
info.priority = ModulePriority::Platform;
|
info.priority = ModulePriority::Core;
|
||||||
info.enabled = true;
|
info.enabled = true;
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
std::string getConfigSectionName() const override {
|
||||||
* @brief 获取配置节名称
|
return "window";
|
||||||
* @return 配置节名称字符串
|
}
|
||||||
*/
|
|
||||||
std::string getConfigSectionName() const override { return "window"; }
|
bool validate() const override {
|
||||||
|
return windowConfig.width > 0 && windowConfig.height > 0;
|
||||||
/**
|
}
|
||||||
* @brief 验证配置有效性
|
|
||||||
* @return 如果配置有效返回 true
|
void resetToDefaults() override {
|
||||||
*/
|
backend = "sdl2";
|
||||||
bool validate() const override;
|
windowConfig = WindowConfigData{};
|
||||||
|
}
|
||||||
/**
|
|
||||||
* @brief 应用平台约束
|
|
||||||
* 根据平台特性调整配置
|
|
||||||
* @param platform 目标平台类型
|
|
||||||
*/
|
|
||||||
void applyPlatformConstraints(PlatformType platform) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 重置为默认配置
|
|
||||||
*/
|
|
||||||
void resetToDefaults() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从 JSON 数据加载配置
|
|
||||||
* @param jsonData JSON 数据指针
|
|
||||||
* @return 加载成功返回 true
|
|
||||||
*/
|
|
||||||
bool loadFromJson(const void* jsonData) override;
|
bool loadFromJson(const void* jsonData) override;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 保存配置到 JSON 数据
|
|
||||||
* @param jsonData JSON 数据指针
|
|
||||||
* @return 保存成功返回 true
|
|
||||||
*/
|
|
||||||
bool saveToJson(void* jsonData) const override;
|
bool saveToJson(void* jsonData) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 窗口模块初始化器
|
|
||||||
* 实现 IModuleInitializer 接口
|
|
||||||
*/
|
|
||||||
class WindowModuleInitializer : public IModuleInitializer {
|
class WindowModuleInitializer : public IModuleInitializer {
|
||||||
public:
|
public:
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
*/
|
|
||||||
WindowModuleInitializer();
|
WindowModuleInitializer();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*/
|
|
||||||
~WindowModuleInitializer() override;
|
~WindowModuleInitializer() override;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取模块标识符
|
|
||||||
* @return 模块唯一标识符
|
|
||||||
*/
|
|
||||||
ModuleId getModuleId() const override { return moduleId_; }
|
ModuleId getModuleId() const override { return moduleId_; }
|
||||||
|
ModulePriority getPriority() const override { return ModulePriority::Core; }
|
||||||
/**
|
|
||||||
* @brief 获取模块优先级
|
|
||||||
* @return 模块优先级
|
|
||||||
*/
|
|
||||||
ModulePriority getPriority() const override { return ModulePriority::Platform; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取模块依赖列表
|
|
||||||
* @return 依赖模块标识符列表
|
|
||||||
*/
|
|
||||||
std::vector<ModuleId> getDependencies() const override { return {}; }
|
std::vector<ModuleId> getDependencies() const override { return {}; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化模块
|
|
||||||
* @param config 模块配置指针
|
|
||||||
* @return 初始化成功返回 true
|
|
||||||
*/
|
|
||||||
bool initialize(const IModuleConfig* config) override;
|
bool initialize(const IModuleConfig* config) override;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭模块
|
|
||||||
*/
|
|
||||||
void shutdown() override;
|
void shutdown() override;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查模块是否已初始化
|
|
||||||
* @return 已初始化返回 true
|
|
||||||
*/
|
|
||||||
bool isInitialized() const override { return initialized_; }
|
bool isInitialized() const override { return initialized_; }
|
||||||
|
|
||||||
/**
|
void setModuleId(ModuleId id) { moduleId_ = id; }
|
||||||
* @brief 获取窗口实例
|
void setWindowConfig(const WindowConfigData& config) { windowConfig_ = config; }
|
||||||
* @return 窗口接口指针
|
|
||||||
*/
|
|
||||||
IWindow* getWindow() const { return window_.get(); }
|
IWindow* getWindow() const { return window_.get(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool initBackend();
|
||||||
|
bool createWindow(const std::string& backend, const WindowConfigData& config);
|
||||||
|
void shutdownBackend();
|
||||||
|
|
||||||
ModuleId moduleId_ = INVALID_MODULE_ID;
|
ModuleId moduleId_ = INVALID_MODULE_ID;
|
||||||
UniquePtr<IWindow> window_;
|
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
|
bool backendInitialized_ = false;
|
||||||
|
std::string backend_;
|
||||||
|
WindowConfigData windowConfig_;
|
||||||
|
UniquePtr<IWindow> window_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ModuleId get_window_module_id();
|
||||||
|
void register_window_module();
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -6,254 +6,186 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
// SDL2 日志头文件
|
|
||||||
#include <SDL.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 日志级别枚举 - 映射到 SDL_LogPriority
|
|
||||||
// ============================================================================
|
|
||||||
enum class LogLevel {
|
enum class LogLevel {
|
||||||
Trace = SDL_LOG_PRIORITY_VERBOSE, // SDL 详细日志
|
Trace = 0,
|
||||||
Debug = SDL_LOG_PRIORITY_DEBUG, // SDL 调试日志
|
Debug = 1,
|
||||||
Info = SDL_LOG_PRIORITY_INFO, // SDL 信息日志
|
Info = 2,
|
||||||
Warn = SDL_LOG_PRIORITY_WARN, // SDL 警告日志
|
Warn = 3,
|
||||||
Error = SDL_LOG_PRIORITY_ERROR, // SDL 错误日志
|
Error = 4,
|
||||||
Fatal = SDL_LOG_PRIORITY_CRITICAL, // SDL 严重日志
|
Fatal = 5,
|
||||||
Off = SDL_LOG_PRIORITY_CRITICAL + 1 // 关闭日志 (使用 Critical+1 作为关闭标记)
|
Off = 6
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 简单的 fmt-style {} 格式化器
|
|
||||||
// ============================================================================
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
// 将单个参数转为字符串
|
|
||||||
template <typename T> inline std::string to_string_arg(const T &value) {
|
template <typename T> inline std::string to_string_arg(const T &value) {
|
||||||
if constexpr (std::is_same_v<T, std::string>) {
|
if constexpr (std::is_same_v<T, std::string>) {
|
||||||
return value;
|
return value;
|
||||||
} else if constexpr (std::is_same_v<T, const char *> ||
|
} else if constexpr (std::is_same_v<T, const char *> ||
|
||||||
std::is_same_v<T, char *>) {
|
std::is_same_v<T, char *>) {
|
||||||
return value ? std::string(value) : std::string("(null)");
|
return value ? std::string(value) : std::string("(null)");
|
||||||
} else if constexpr (std::is_same_v<T, bool>) {
|
} else if constexpr (std::is_same_v<T, bool>) {
|
||||||
return value ? "true" : "false";
|
return value ? "true" : "false";
|
||||||
} else if constexpr (std::is_arithmetic_v<T>) {
|
} else if constexpr (std::is_arithmetic_v<T>) {
|
||||||
// 对浮点数使用特殊格式
|
if constexpr (std::is_floating_point_v<T>) {
|
||||||
if constexpr (std::is_floating_point_v<T>) {
|
char buf[64];
|
||||||
char buf[64];
|
snprintf(buf, sizeof(buf), "%.2f", static_cast<double>(value));
|
||||||
snprintf(buf, sizeof(buf), "%.2f", static_cast<double>(value));
|
return buf;
|
||||||
return buf;
|
} else {
|
||||||
|
return std::to_string(value);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return std::to_string(value);
|
std::ostringstream oss;
|
||||||
|
oss << value;
|
||||||
|
return oss.str();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
std::ostringstream oss;
|
|
||||||
oss << value;
|
|
||||||
return oss.str();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化基础情况:没有更多参数
|
|
||||||
inline std::string format_impl(const char *fmt) {
|
inline std::string format_impl(const char *fmt) {
|
||||||
std::string result;
|
std::string result;
|
||||||
while (*fmt) {
|
while (*fmt) {
|
||||||
if (*fmt == '{' && *(fmt + 1) == '}') {
|
if (*fmt == '{' && *(fmt + 1) == '}') {
|
||||||
result += "{}"; // 无参数可替换,保留原样
|
result += "{}";
|
||||||
fmt += 2;
|
fmt += 2;
|
||||||
} else {
|
} else {
|
||||||
result += *fmt;
|
result += *fmt;
|
||||||
++fmt;
|
++fmt;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return result;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化递归:替换第一个 {} 并递归处理剩余
|
|
||||||
template <typename T, typename... Args>
|
template <typename T, typename... Args>
|
||||||
inline std::string format_impl(const char *fmt, const T &first,
|
inline std::string format_impl(const char *fmt, const T &first,
|
||||||
const Args &...rest) {
|
const Args &...rest) {
|
||||||
std::string result;
|
std::string result;
|
||||||
while (*fmt) {
|
while (*fmt) {
|
||||||
if (*fmt == '{') {
|
if (*fmt == '{') {
|
||||||
// 检查 {:#x} 等格式说明符
|
if (*(fmt + 1) == '}') {
|
||||||
if (*(fmt + 1) == '}') {
|
result += to_string_arg(first);
|
||||||
result += to_string_arg(first);
|
fmt += 2;
|
||||||
fmt += 2;
|
result += format_impl(fmt, rest...);
|
||||||
result += format_impl(fmt, rest...);
|
return result;
|
||||||
return result;
|
} else if (*(fmt + 1) == ':') {
|
||||||
} else if (*(fmt + 1) == ':') {
|
const char *end = fmt + 2;
|
||||||
// 跳过格式说明符直到 }
|
while (*end && *end != '}')
|
||||||
const char *end = fmt + 2;
|
++end;
|
||||||
while (*end && *end != '}')
|
if (*end == '}') {
|
||||||
++end;
|
std::string spec(fmt + 2, end);
|
||||||
if (*end == '}') {
|
if (spec.find('x') != std::string::npos ||
|
||||||
// 检查是否是十六进制格式
|
spec.find('X') != std::string::npos) {
|
||||||
std::string spec(fmt + 2, end);
|
if constexpr (std::is_integral_v<T>) {
|
||||||
if (spec.find('x') != std::string::npos ||
|
char buf[32];
|
||||||
spec.find('X') != std::string::npos) {
|
snprintf(buf, sizeof(buf), "0x%x",
|
||||||
if constexpr (std::is_integral_v<T>) {
|
static_cast<unsigned int>(first));
|
||||||
char buf[32];
|
result += buf;
|
||||||
snprintf(buf, sizeof(buf), "0x%x",
|
} else {
|
||||||
static_cast<unsigned int>(first));
|
result += to_string_arg(first);
|
||||||
result += buf;
|
}
|
||||||
} else {
|
} else if (spec.find('f') != std::string::npos ||
|
||||||
result += to_string_arg(first);
|
spec.find('.') != std::string::npos) {
|
||||||
}
|
if constexpr (std::is_arithmetic_v<T>) {
|
||||||
} else if (spec.find('f') != std::string::npos ||
|
int precision = 2;
|
||||||
spec.find('.') != std::string::npos) {
|
auto dot = spec.find('.');
|
||||||
if constexpr (std::is_arithmetic_v<T>) {
|
if (dot != std::string::npos) {
|
||||||
// 解析精度
|
precision = 0;
|
||||||
int precision = 2;
|
for (size_t i = dot + 1;
|
||||||
auto dot = spec.find('.');
|
i < spec.size() && spec[i] >= '0' && spec[i] <= '9'; ++i) {
|
||||||
if (dot != std::string::npos) {
|
precision = precision * 10 + (spec[i] - '0');
|
||||||
precision = 0;
|
}
|
||||||
for (size_t i = dot + 1;
|
}
|
||||||
i < spec.size() && spec[i] >= '0' && spec[i] <= '9'; ++i) {
|
char fmtbuf[16];
|
||||||
precision = precision * 10 + (spec[i] - '0');
|
snprintf(fmtbuf, sizeof(fmtbuf), "%%.%df", precision);
|
||||||
|
char buf[64];
|
||||||
|
snprintf(buf, sizeof(buf), fmtbuf, static_cast<double>(first));
|
||||||
|
result += buf;
|
||||||
|
} else {
|
||||||
|
result += to_string_arg(first);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result += to_string_arg(first);
|
||||||
|
}
|
||||||
|
fmt = end + 1;
|
||||||
|
result += format_impl(fmt, rest...);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
char fmtbuf[16];
|
|
||||||
snprintf(fmtbuf, sizeof(fmtbuf), "%%.%df", precision);
|
|
||||||
char buf[64];
|
|
||||||
snprintf(buf, sizeof(buf), fmtbuf, static_cast<double>(first));
|
|
||||||
result += buf;
|
|
||||||
} else {
|
|
||||||
result += to_string_arg(first);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
result += to_string_arg(first);
|
|
||||||
}
|
|
||||||
fmt = end + 1;
|
|
||||||
result += format_impl(fmt, rest...);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
result += *fmt;
|
||||||
|
++fmt;
|
||||||
}
|
}
|
||||||
result += *fmt;
|
return result;
|
||||||
++fmt;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
// 顶层格式化函数
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
inline std::string e2d_format(const char *fmt, const Args &...args) {
|
inline std::string e2d_format(const char *fmt, const Args &...args) {
|
||||||
return detail::format_impl(fmt, args...);
|
return detail::format_impl(fmt, args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 无参数版本
|
|
||||||
inline std::string e2d_format(const char *fmt) { return std::string(fmt); }
|
inline std::string e2d_format(const char *fmt) { return std::string(fmt); }
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Logger 类 - 使用 SDL2 日志系统
|
|
||||||
// ============================================================================
|
|
||||||
class Logger {
|
class Logger {
|
||||||
public:
|
public:
|
||||||
/**
|
static void init();
|
||||||
* @brief 初始化日志系统
|
static void shutdown();
|
||||||
*/
|
|
||||||
static void init();
|
|
||||||
|
|
||||||
/**
|
static void setLevel(LogLevel level);
|
||||||
* @brief 关闭日志系统
|
static void setConsoleOutput(bool enable);
|
||||||
*/
|
static void setFileOutput(const std::string &filename);
|
||||||
static void shutdown();
|
|
||||||
|
|
||||||
/**
|
static LogLevel getLevel() { return level_; }
|
||||||
* @brief 设置日志级别
|
|
||||||
* @param level 日志级别
|
|
||||||
*/
|
|
||||||
static void setLevel(LogLevel level);
|
|
||||||
|
|
||||||
/**
|
template <typename... Args>
|
||||||
* @brief 设置是否输出到控制台
|
static void log(LogLevel level, const char *fmt, const Args &...args) {
|
||||||
* @param enable 是否启用
|
if (static_cast<int>(level) < static_cast<int>(level_))
|
||||||
*/
|
return;
|
||||||
static void setConsoleOutput(bool enable);
|
std::string msg = e2d_format(fmt, args...);
|
||||||
|
outputLog(level, msg.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
static void log(LogLevel level, const char *msg) {
|
||||||
* @brief 设置日志输出到文件
|
if (static_cast<int>(level) < static_cast<int>(level_))
|
||||||
* @param filename 日志文件名
|
return;
|
||||||
*/
|
outputLog(level, msg);
|
||||||
static void setFileOutput(const std::string &filename);
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前日志级别
|
|
||||||
* @return 当前日志级别
|
|
||||||
*/
|
|
||||||
static LogLevel getLevel() { return level_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 日志记录模板函数
|
|
||||||
* @param level 日志级别
|
|
||||||
* @param fmt 格式化字符串
|
|
||||||
* @param args 可变参数
|
|
||||||
*/
|
|
||||||
template <typename... Args>
|
|
||||||
static void log(LogLevel level, const char *fmt, const Args &...args) {
|
|
||||||
if (static_cast<int>(level) < static_cast<int>(level_))
|
|
||||||
return;
|
|
||||||
std::string msg = e2d_format(fmt, args...);
|
|
||||||
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
static_cast<SDL_LogPriority>(level), "[%s] %s",
|
|
||||||
getLevelString(level), msg.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 日志记录无参数版本
|
|
||||||
* @param level 日志级别
|
|
||||||
* @param msg 日志消息
|
|
||||||
*/
|
|
||||||
static void log(LogLevel level, const char *msg) {
|
|
||||||
if (static_cast<int>(level) < static_cast<int>(level_))
|
|
||||||
return;
|
|
||||||
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
static_cast<SDL_LogPriority>(level), "[%s] %s",
|
|
||||||
getLevelString(level), msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static LogLevel level_; // 当前日志级别
|
static void outputLog(LogLevel level, const char *msg);
|
||||||
static bool initialized_; // 是否已初始化
|
static const char *getLevelString(LogLevel level);
|
||||||
static bool consoleOutput_; // 是否输出到控制台
|
static void writeToConsole(LogLevel level, const char *msg);
|
||||||
static bool fileOutput_; // 是否输出到文件
|
static void writeToFile(LogLevel level, const char *msg);
|
||||||
static std::string logFile_; // 日志文件路径
|
|
||||||
|
|
||||||
/**
|
static LogLevel level_;
|
||||||
* @brief 获取日志级别字符串
|
static bool initialized_;
|
||||||
* @param level 日志级别
|
static bool consoleOutput_;
|
||||||
* @return 级别字符串
|
static bool fileOutput_;
|
||||||
*/
|
static std::string logFile_;
|
||||||
static const char *getLevelString(LogLevel level);
|
static void *logFileHandle_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 日志宏
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
#ifdef E2D_DEBUG
|
#ifdef E2D_DEBUG
|
||||||
#define E2D_LOG_TRACE(...) \
|
#define E2D_LOG_TRACE(...) \
|
||||||
::extra2d::Logger::log(::extra2d::LogLevel::Trace, __VA_ARGS__)
|
::extra2d::Logger::log(::extra2d::LogLevel::Trace, __VA_ARGS__)
|
||||||
#define E2D_LOG_DEBUG(...) \
|
#define E2D_LOG_DEBUG(...) \
|
||||||
::extra2d::Logger::log(::extra2d::LogLevel::Debug, __VA_ARGS__)
|
::extra2d::Logger::log(::extra2d::LogLevel::Debug, __VA_ARGS__)
|
||||||
#else
|
#else
|
||||||
#define E2D_LOG_TRACE(...)
|
#define E2D_LOG_TRACE(...)
|
||||||
#define E2D_LOG_DEBUG(...)
|
#define E2D_LOG_DEBUG(...)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define E2D_LOG_INFO(...) \
|
#define E2D_LOG_INFO(...) \
|
||||||
::extra2d::Logger::log(::extra2d::LogLevel::Info, __VA_ARGS__)
|
::extra2d::Logger::log(::extra2d::LogLevel::Info, __VA_ARGS__)
|
||||||
#define E2D_LOG_WARN(...) \
|
#define E2D_LOG_WARN(...) \
|
||||||
::extra2d::Logger::log(::extra2d::LogLevel::Warn, __VA_ARGS__)
|
::extra2d::Logger::log(::extra2d::LogLevel::Warn, __VA_ARGS__)
|
||||||
#define E2D_LOG_ERROR(...) \
|
#define E2D_LOG_ERROR(...) \
|
||||||
::extra2d::Logger::log(::extra2d::LogLevel::Error, __VA_ARGS__)
|
::extra2d::Logger::log(::extra2d::LogLevel::Error, __VA_ARGS__)
|
||||||
#define E2D_LOG_FATAL(...) \
|
#define E2D_LOG_FATAL(...) \
|
||||||
::extra2d::Logger::log(::extra2d::LogLevel::Fatal, __VA_ARGS__)
|
::extra2d::Logger::log(::extra2d::LogLevel::Fatal, __VA_ARGS__)
|
||||||
|
|
||||||
// 简化的日志宏
|
|
||||||
#define E2D_INFO(...) E2D_LOG_INFO(__VA_ARGS__)
|
#define E2D_INFO(...) E2D_LOG_INFO(__VA_ARGS__)
|
||||||
#define E2D_WARN(...) E2D_LOG_WARN(__VA_ARGS__)
|
#define E2D_WARN(...) E2D_LOG_WARN(__VA_ARGS__)
|
||||||
#define E2D_ERROR(...) E2D_LOG_ERROR(__VA_ARGS__)
|
#define E2D_ERROR(...) E2D_LOG_ERROR(__VA_ARGS__)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/config/module_config.h>
|
||||||
|
#include <extra2d/config/module_initializer.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
class LoggerModuleConfig : public IModuleConfig {
|
||||||
|
public:
|
||||||
|
LogLevel logLevel = LogLevel::Info;
|
||||||
|
bool consoleOutput = true;
|
||||||
|
bool fileOutput = false;
|
||||||
|
std::string logFilePath;
|
||||||
|
|
||||||
|
ModuleInfo getModuleInfo() const override {
|
||||||
|
ModuleInfo info;
|
||||||
|
info.id = 0;
|
||||||
|
info.name = "Logger";
|
||||||
|
info.version = "1.0.0";
|
||||||
|
info.priority = ModulePriority::Core;
|
||||||
|
info.enabled = true;
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getConfigSectionName() const override {
|
||||||
|
return "logger";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validate() const override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetToDefaults() override {
|
||||||
|
logLevel = LogLevel::Info;
|
||||||
|
consoleOutput = true;
|
||||||
|
fileOutput = false;
|
||||||
|
logFilePath.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loadFromJson(const void* jsonData) override;
|
||||||
|
bool saveToJson(void* jsonData) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LoggerModuleInitializer : public IModuleInitializer {
|
||||||
|
public:
|
||||||
|
LoggerModuleInitializer();
|
||||||
|
~LoggerModuleInitializer() override;
|
||||||
|
|
||||||
|
ModuleId getModuleId() const override { return moduleId_; }
|
||||||
|
ModulePriority getPriority() const override { return ModulePriority::Core; }
|
||||||
|
std::vector<ModuleId> getDependencies() const override { return {}; }
|
||||||
|
|
||||||
|
bool initialize(const IModuleConfig* config) override;
|
||||||
|
void shutdown() override;
|
||||||
|
bool isInitialized() const override { return initialized_; }
|
||||||
|
|
||||||
|
void setModuleId(ModuleId id) { moduleId_ = id; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
ModuleId moduleId_ = INVALID_MODULE_ID;
|
||||||
|
bool initialized_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,39 +1,23 @@
|
||||||
#include <extra2d/app/application.h>
|
#include <extra2d/app/application.h>
|
||||||
#include <extra2d/config/config_manager.h>
|
#include <extra2d/config/config_module.h>
|
||||||
#include <extra2d/config/module_registry.h>
|
#include <extra2d/config/module_registry.h>
|
||||||
#include <extra2d/event/event_dispatcher.h>
|
#include <extra2d/event/event_dispatcher.h>
|
||||||
#include <extra2d/event/event_queue.h>
|
#include <extra2d/event/event_queue.h>
|
||||||
#include <extra2d/graphics/camera.h>
|
#include <extra2d/graphics/camera.h>
|
||||||
#include <extra2d/graphics/render_backend.h>
|
#include <extra2d/graphics/render_module.h>
|
||||||
#include <extra2d/graphics/viewport_adapter.h>
|
#include <extra2d/graphics/viewport_adapter.h>
|
||||||
#include <extra2d/graphics/vram_manager.h>
|
#include <extra2d/graphics/vram_manager.h>
|
||||||
#include <extra2d/platform/iinput.h>
|
#include <extra2d/platform/iinput.h>
|
||||||
#include <extra2d/platform/platform_module.h>
|
#include <extra2d/platform/platform_init_module.h>
|
||||||
|
#include <extra2d/platform/window_module.h>
|
||||||
#include <extra2d/scene/scene_manager.h>
|
#include <extra2d/scene/scene_manager.h>
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
#include <extra2d/utils/timer.h>
|
#include <extra2d/utils/timer.h>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include <SDL.h>
|
|
||||||
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
#include <switch.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef E2D_BACKEND_SDL2
|
|
||||||
namespace extra2d {
|
|
||||||
void initSDL2Backend();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前时间(秒)
|
|
||||||
* @return 当前时间戳(秒)
|
|
||||||
*/
|
|
||||||
static double getTimeSeconds() {
|
static double getTimeSeconds() {
|
||||||
#ifdef __SWITCH__
|
#ifdef __SWITCH__
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
|
|
@ -67,170 +51,97 @@ bool Application::init(const AppConfig& config) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER) != 0) {
|
register_config_module();
|
||||||
E2D_LOG_ERROR("Failed to initialize SDL: {}", SDL_GetError());
|
register_platform_module();
|
||||||
return false;
|
register_window_module();
|
||||||
|
register_render_module();
|
||||||
|
|
||||||
|
auto* configInit = ModuleRegistry::instance().getInitializer(get_config_module_id());
|
||||||
|
if (configInit) {
|
||||||
|
auto* cfgInit = dynamic_cast<ConfigModuleInitializer*>(configInit);
|
||||||
|
if (cfgInit) {
|
||||||
|
cfgInit->setAppConfig(config);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger::init();
|
return initModules();
|
||||||
|
|
||||||
E2D_LOG_INFO("Initializing application with config...");
|
|
||||||
|
|
||||||
if (!ConfigManager::instance().initialize()) {
|
|
||||||
E2D_LOG_ERROR("Failed to initialize ConfigManager");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager::instance().setAppConfig(config);
|
|
||||||
|
|
||||||
return initImpl();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::init(const std::string& configPath) {
|
bool Application::init(const std::string& configPath) {
|
||||||
if (initialized_) {
|
if (initialized_) {
|
||||||
E2D_LOG_WARN("Application already initialized");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
E2D_LOG_INFO("Initializing application from config file: {}", configPath);
|
register_config_module();
|
||||||
|
register_platform_module();
|
||||||
|
register_window_module();
|
||||||
|
register_render_module();
|
||||||
|
|
||||||
if (!ConfigManager::instance().initialize(configPath)) {
|
auto* configInit = ModuleRegistry::instance().getInitializer(get_config_module_id());
|
||||||
E2D_LOG_WARN("Failed to load config from file, using defaults");
|
if (configInit) {
|
||||||
if (!ConfigManager::instance().initialize()) {
|
auto* cfgInit = dynamic_cast<ConfigModuleInitializer*>(configInit);
|
||||||
E2D_LOG_ERROR("Failed to initialize ConfigManager");
|
if (cfgInit) {
|
||||||
return false;
|
cfgInit->setConfigPath(configPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return initImpl();
|
return initModules();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::initImpl() {
|
bool Application::initModules() {
|
||||||
#ifdef E2D_BACKEND_SDL2
|
|
||||||
initSDL2Backend();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
auto& configMgr = ConfigManager::instance();
|
|
||||||
AppConfig& appConfig = configMgr.appConfig();
|
|
||||||
|
|
||||||
PlatformType platform = appConfig.targetPlatform;
|
|
||||||
if (platform == PlatformType::Auto) {
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
platform = PlatformType::Switch;
|
|
||||||
#else
|
|
||||||
#ifdef _WIN32
|
|
||||||
platform = PlatformType::Windows;
|
|
||||||
#elif defined(__linux__)
|
|
||||||
platform = PlatformType::Linux;
|
|
||||||
#elif defined(__APPLE__)
|
|
||||||
platform = PlatformType::macOS;
|
|
||||||
#else
|
|
||||||
platform = PlatformType::Windows;
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_LOG_INFO("Target platform: {} ({})", getPlatformTypeName(platform),
|
|
||||||
static_cast<int>(platform));
|
|
||||||
|
|
||||||
UniquePtr<PlatformConfig> platformConfig = createPlatformConfig(platform);
|
|
||||||
if (!platformConfig) {
|
|
||||||
E2D_LOG_ERROR("Failed to create platform config");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
appConfig.applyPlatformConstraints(*platformConfig);
|
|
||||||
|
|
||||||
const auto& capabilities = platformConfig->capabilities();
|
|
||||||
E2D_LOG_INFO("Platform capabilities: windowed={}, fullscreen={}, cursor={}, DPI={}",
|
|
||||||
capabilities.supportsWindowed, capabilities.supportsFullscreen,
|
|
||||||
capabilities.supportsCursor, capabilities.supportsDPIAwareness);
|
|
||||||
|
|
||||||
if (platform == PlatformType::Switch) {
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
Result rc;
|
|
||||||
rc = romfsInit();
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
E2D_LOG_INFO("RomFS initialized successfully");
|
|
||||||
} else {
|
|
||||||
E2D_LOG_WARN("romfsInit failed: {:#08X}, will use regular filesystem", rc);
|
|
||||||
}
|
|
||||||
|
|
||||||
rc = socketInitializeDefault();
|
|
||||||
if (R_FAILED(rc)) {
|
|
||||||
E2D_LOG_WARN("socketInitializeDefault failed, nxlink will not be available");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
auto initOrder = ModuleRegistry::instance().getInitializationOrder();
|
auto initOrder = ModuleRegistry::instance().getInitializationOrder();
|
||||||
E2D_LOG_INFO("Initializing {} registered modules...", initOrder.size());
|
|
||||||
|
|
||||||
for (ModuleId moduleId : initOrder) {
|
for (ModuleId moduleId : initOrder) {
|
||||||
auto initializer = ModuleRegistry::instance().createInitializer(moduleId);
|
auto* initializer = ModuleRegistry::instance().getInitializer(moduleId);
|
||||||
if (!initializer) {
|
if (!initializer) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* moduleConfig = ModuleRegistry::instance().getModuleConfig(moduleId);
|
auto* moduleConfig = ModuleRegistry::instance().getModuleConfig(moduleId);
|
||||||
if (!moduleConfig) {
|
if (!moduleConfig) {
|
||||||
E2D_LOG_WARN("Module {} has no config, skipping", moduleId);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto info = moduleConfig->getModuleInfo();
|
auto info = moduleConfig->getModuleInfo();
|
||||||
if (!info.enabled) {
|
if (!info.enabled) {
|
||||||
E2D_LOG_INFO("Module '{}' is disabled, skipping", info.name);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
E2D_LOG_INFO("Initializing module '{}' (priority: {})",
|
if (info.name == "Render") {
|
||||||
info.name, static_cast<int>(info.priority));
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!initializer->initialize(moduleConfig)) {
|
if (!initializer->initialize(moduleConfig)) {
|
||||||
E2D_LOG_ERROR("Failed to initialize module '{}'", info.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string backend = "sdl2";
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
backend = "switch";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!BackendFactory::has(backend)) {
|
|
||||||
E2D_LOG_ERROR("Backend '{}' not available", backend);
|
|
||||||
auto backends = BackendFactory::backends();
|
|
||||||
if (backends.empty()) {
|
|
||||||
E2D_LOG_ERROR("No backends registered!");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
backend = backends[0];
|
|
||||||
E2D_LOG_WARN("Using fallback backend: {}", backend);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window_ = BackendFactory::createWindow(backend);
|
auto* windowInit = ModuleRegistry::instance().getInitializer(get_window_module_id());
|
||||||
|
if (!windowInit || !windowInit->isInitialized()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* windowModule = dynamic_cast<WindowModuleInitializer*>(windowInit);
|
||||||
|
if (!windowModule) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
window_ = windowModule->getWindow();
|
||||||
if (!window_) {
|
if (!window_) {
|
||||||
E2D_LOG_ERROR("Failed to create window for backend: {}", backend);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowConfigData winConfig = appConfig.window;
|
auto* renderInit = ModuleRegistry::instance().getInitializer(get_render_module_id());
|
||||||
|
if (renderInit) {
|
||||||
if (platform == PlatformType::Switch) {
|
auto* renderModule = dynamic_cast<RenderModuleInitializer*>(renderInit);
|
||||||
winConfig.mode = WindowMode::Fullscreen;
|
if (renderModule) {
|
||||||
winConfig.resizable = false;
|
renderModule->setWindow(window_);
|
||||||
}
|
|
||||||
|
auto* renderConfig = ModuleRegistry::instance().getModuleConfig(get_render_module_id());
|
||||||
if (!window_->create(winConfig)) {
|
if (renderConfig && !renderInit->initialize(renderConfig)) {
|
||||||
E2D_LOG_ERROR("Failed to create window");
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer_ = RenderBackend::create(appConfig.render.backend);
|
|
||||||
if (!renderer_ || !renderer_->init(window_.get())) {
|
|
||||||
E2D_LOG_ERROR("Failed to initialize renderer");
|
|
||||||
window_->destroy();
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sceneManager_ = makeUnique<SceneManager>();
|
sceneManager_ = makeUnique<SceneManager>();
|
||||||
|
|
@ -242,13 +153,12 @@ bool Application::initImpl() {
|
||||||
|
|
||||||
viewportAdapter_ = makeUnique<ViewportAdapter>();
|
viewportAdapter_ = makeUnique<ViewportAdapter>();
|
||||||
ViewportConfig vpConfig;
|
ViewportConfig vpConfig;
|
||||||
vpConfig.logicWidth = static_cast<float>(appConfig.window.width);
|
vpConfig.logicWidth = static_cast<float>(window_->width());
|
||||||
vpConfig.logicHeight = static_cast<float>(appConfig.window.height);
|
vpConfig.logicHeight = static_cast<float>(window_->height());
|
||||||
vpConfig.mode = ViewportMode::AspectRatio;
|
vpConfig.mode = ViewportMode::AspectRatio;
|
||||||
viewportAdapter_->setConfig(vpConfig);
|
viewportAdapter_->setConfig(vpConfig);
|
||||||
|
|
||||||
camera_->setViewportAdapter(viewportAdapter_.get());
|
camera_->setViewportAdapter(viewportAdapter_.get());
|
||||||
|
|
||||||
viewportAdapter_->update(window_->width(), window_->height());
|
viewportAdapter_->update(window_->width(), window_->height());
|
||||||
|
|
||||||
window_->onResize([this](int width, int height) {
|
window_->onResize([this](int width, int height) {
|
||||||
|
|
@ -272,12 +182,6 @@ bool Application::initImpl() {
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
running_ = true;
|
running_ = true;
|
||||||
|
|
||||||
E2D_LOG_INFO("Application initialized successfully");
|
|
||||||
E2D_LOG_INFO(" Window: {}x{}", window_->width(), window_->height());
|
|
||||||
E2D_LOG_INFO(" Backend: {}", backend);
|
|
||||||
E2D_LOG_INFO(" VSync: {}", appConfig.render.vsync);
|
|
||||||
E2D_LOG_INFO(" Target FPS: {}", appConfig.render.targetFPS);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -285,8 +189,6 @@ void Application::shutdown() {
|
||||||
if (!initialized_)
|
if (!initialized_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
E2D_LOG_INFO("Shutting down application...");
|
|
||||||
|
|
||||||
VRAMMgr::get().printStats();
|
VRAMMgr::get().printStats();
|
||||||
|
|
||||||
if (sceneManager_) {
|
if (sceneManager_) {
|
||||||
|
|
@ -301,59 +203,24 @@ void Application::shutdown() {
|
||||||
eventQueue_.reset();
|
eventQueue_.reset();
|
||||||
eventDispatcher_.reset();
|
eventDispatcher_.reset();
|
||||||
|
|
||||||
if (renderer_) {
|
window_ = nullptr;
|
||||||
renderer_->shutdown();
|
|
||||||
renderer_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window_) {
|
|
||||||
window_->destroy();
|
|
||||||
window_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto modules = ModuleRegistry::instance().getAllModules();
|
|
||||||
auto initOrder = ModuleRegistry::instance().getInitializationOrder();
|
auto initOrder = ModuleRegistry::instance().getInitializationOrder();
|
||||||
|
|
||||||
for (auto it = initOrder.rbegin(); it != initOrder.rend(); ++it) {
|
for (auto it = initOrder.rbegin(); it != initOrder.rend(); ++it) {
|
||||||
ModuleId moduleId = *it;
|
ModuleId moduleId = *it;
|
||||||
auto initializer = ModuleRegistry::instance().createInitializer(moduleId);
|
auto* initializer = ModuleRegistry::instance().getInitializer(moduleId);
|
||||||
if (initializer && initializer->isInitialized()) {
|
if (initializer && initializer->isInitialized()) {
|
||||||
auto* moduleConfig = ModuleRegistry::instance().getModuleConfig(moduleId);
|
|
||||||
if (moduleConfig) {
|
|
||||||
auto info = moduleConfig->getModuleInfo();
|
|
||||||
E2D_LOG_INFO("Shutting down module '{}'", info.name);
|
|
||||||
}
|
|
||||||
initializer->shutdown();
|
initializer->shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PlatformType platform = ConfigManager::instance().appConfig().targetPlatform;
|
|
||||||
if (platform == PlatformType::Auto) {
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
platform = PlatformType::Switch;
|
|
||||||
#else
|
|
||||||
platform = PlatformType::Windows;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
if (platform == PlatformType::Switch) {
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
romfsExit();
|
|
||||||
socketExit();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigManager::instance().shutdown();
|
|
||||||
|
|
||||||
initialized_ = false;
|
initialized_ = false;
|
||||||
running_ = false;
|
running_ = false;
|
||||||
|
|
||||||
E2D_LOG_INFO("Application shutdown complete");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::run() {
|
void Application::run() {
|
||||||
if (!initialized_) {
|
if (!initialized_) {
|
||||||
E2D_LOG_ERROR("Application not initialized");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -372,7 +239,6 @@ void Application::quit() {
|
||||||
void Application::pause() {
|
void Application::pause() {
|
||||||
if (!paused_) {
|
if (!paused_) {
|
||||||
paused_ = true;
|
paused_ = true;
|
||||||
E2D_LOG_INFO("Application paused");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -380,7 +246,6 @@ void Application::resume() {
|
||||||
if (paused_) {
|
if (paused_) {
|
||||||
paused_ = false;
|
paused_ = false;
|
||||||
lastFrameTime_ = getTimeSeconds();
|
lastFrameTime_ = getTimeSeconds();
|
||||||
E2D_LOG_INFO("Application resumed");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -436,24 +301,30 @@ void Application::update() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::render() {
|
void Application::render() {
|
||||||
if (!renderer_) {
|
auto* renderInit = ModuleRegistry::instance().getInitializer(get_render_module_id());
|
||||||
E2D_LOG_ERROR("Render failed: renderer is null");
|
RenderBackend* renderer = nullptr;
|
||||||
|
if (renderInit) {
|
||||||
|
auto* renderModule = dynamic_cast<RenderModuleInitializer*>(renderInit);
|
||||||
|
if (renderModule) {
|
||||||
|
renderer = renderModule->getRenderer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!renderer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewportAdapter_) {
|
if (viewportAdapter_) {
|
||||||
const auto& vp = viewportAdapter_->getViewport();
|
const auto& vp = viewportAdapter_->getViewport();
|
||||||
renderer_->setViewport(
|
renderer->setViewport(
|
||||||
static_cast<int>(vp.origin.x), static_cast<int>(vp.origin.y),
|
static_cast<int>(vp.origin.x), static_cast<int>(vp.origin.y),
|
||||||
static_cast<int>(vp.size.width), static_cast<int>(vp.size.height));
|
static_cast<int>(vp.size.width), static_cast<int>(vp.size.height));
|
||||||
} else {
|
} else {
|
||||||
renderer_->setViewport(0, 0, window_->width(), window_->height());
|
renderer->setViewport(0, 0, window_->width(), window_->height());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sceneManager_) {
|
if (sceneManager_) {
|
||||||
sceneManager_->render(*renderer_);
|
sceneManager_->render(*renderer);
|
||||||
} else {
|
|
||||||
E2D_LOG_WARN("Render: sceneManager is null");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window_->swap();
|
window_->swap();
|
||||||
|
|
@ -463,6 +334,21 @@ IInput& Application::input() {
|
||||||
return *window_->input();
|
return *window_->input();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RenderBackend& Application::renderer() {
|
||||||
|
auto* renderInit = ModuleRegistry::instance().getInitializer(get_render_module_id());
|
||||||
|
if (renderInit) {
|
||||||
|
auto* renderModule = dynamic_cast<RenderModuleInitializer*>(renderInit);
|
||||||
|
if (renderModule && renderModule->getRenderer()) {
|
||||||
|
return *renderModule->getRenderer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static RenderBackend* dummy = nullptr;
|
||||||
|
if (!dummy) {
|
||||||
|
dummy = RenderBackend::create(BackendType::OpenGL).release();
|
||||||
|
}
|
||||||
|
return *dummy;
|
||||||
|
}
|
||||||
|
|
||||||
SceneManager& Application::scenes() {
|
SceneManager& Application::scenes() {
|
||||||
return *sceneManager_;
|
return *sceneManager_;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
#include <extra2d/config/config_module.h>
|
||||||
|
#include <extra2d/config/module_registry.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
static ModuleId s_configModuleId = INVALID_MODULE_ID;
|
||||||
|
|
||||||
|
ModuleId get_config_module_id() {
|
||||||
|
return s_configModuleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConfigModuleConfig::loadFromJson(const void* jsonData) {
|
||||||
|
if (!jsonData) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const json& j = *static_cast<const json*>(jsonData);
|
||||||
|
|
||||||
|
if (j.contains("configPath")) {
|
||||||
|
configPath = j["configPath"].get<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConfigModuleConfig::saveToJson(void* jsonData) const {
|
||||||
|
if (!jsonData) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
json& j = *static_cast<json*>(jsonData);
|
||||||
|
j["configPath"] = configPath;
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigModuleInitializer::ConfigModuleInitializer()
|
||||||
|
: moduleId_(INVALID_MODULE_ID)
|
||||||
|
, initialized_(false) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigModuleInitializer::~ConfigModuleInitializer() {
|
||||||
|
if (initialized_) {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConfigModuleInitializer::initialize(const IModuleConfig* config) {
|
||||||
|
if (initialized_) return true;
|
||||||
|
|
||||||
|
const ConfigModuleConfig* configModule = dynamic_cast<const ConfigModuleConfig*>(config);
|
||||||
|
|
||||||
|
if (!configPath_.empty()) {
|
||||||
|
if (!ConfigManager::instance().initialize(configPath_)) {
|
||||||
|
if (!ConfigManager::instance().initialize()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!ConfigManager::instance().initialize()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configModule && !configModule->appConfig.appName.empty()) {
|
||||||
|
ConfigManager::instance().setAppConfig(configModule->appConfig);
|
||||||
|
} else if (!appConfig_.appName.empty()) {
|
||||||
|
ConfigManager::instance().setAppConfig(appConfig_);
|
||||||
|
}
|
||||||
|
|
||||||
|
initialized_ = true;
|
||||||
|
E2D_LOG_INFO("Config module initialized");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigModuleInitializer::shutdown() {
|
||||||
|
if (!initialized_) return;
|
||||||
|
|
||||||
|
E2D_LOG_INFO("Config module shutting down");
|
||||||
|
ConfigManager::instance().shutdown();
|
||||||
|
initialized_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void register_config_module() {
|
||||||
|
if (s_configModuleId != INVALID_MODULE_ID) return;
|
||||||
|
|
||||||
|
s_configModuleId = ModuleRegistry::instance().registerModule(
|
||||||
|
makeUnique<ConfigModuleConfig>(),
|
||||||
|
[]() -> UniquePtr<IModuleInitializer> {
|
||||||
|
auto initializer = makeUnique<ConfigModuleInitializer>();
|
||||||
|
initializer->setModuleId(s_configModuleId);
|
||||||
|
return initializer;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct ConfigModuleAutoRegister {
|
||||||
|
ConfigModuleAutoRegister() {
|
||||||
|
register_config_module();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static ConfigModuleAutoRegister s_autoRegister;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#include <extra2d/config/module_registry.h>
|
#include <extra2d/config/module_registry.h>
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
@ -26,7 +26,7 @@ ModuleId ModuleRegistry::registerModule(
|
||||||
ModuleInitializerFactory initializerFactory
|
ModuleInitializerFactory initializerFactory
|
||||||
) {
|
) {
|
||||||
if (!config) {
|
if (!config) {
|
||||||
E2D_LOG_ERROR("Cannot register null module config");
|
std::fprintf(stderr, "[ERROR] Cannot register null module config\n");
|
||||||
return INVALID_MODULE_ID;
|
return INVALID_MODULE_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,7 +35,7 @@ ModuleId ModuleRegistry::registerModule(
|
||||||
ModuleInfo info = config->getModuleInfo();
|
ModuleInfo info = config->getModuleInfo();
|
||||||
|
|
||||||
if (nameToId_.find(info.name) != nameToId_.end()) {
|
if (nameToId_.find(info.name) != nameToId_.end()) {
|
||||||
E2D_LOG_ERROR("Module '{}' already registered", info.name);
|
std::fprintf(stderr, "[ERROR] Module '%s' already registered\n", info.name.c_str());
|
||||||
return INVALID_MODULE_ID;
|
return INVALID_MODULE_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,7 +50,6 @@ ModuleId ModuleRegistry::registerModule(
|
||||||
modules_[id] = std::move(entry);
|
modules_[id] = std::move(entry);
|
||||||
nameToId_[info.name] = id;
|
nameToId_[info.name] = id;
|
||||||
|
|
||||||
E2D_LOG_INFO("Registered module '{}' with id {}", info.name, id);
|
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,7 +64,6 @@ bool ModuleRegistry::unregisterModule(ModuleId id) {
|
||||||
|
|
||||||
auto it = modules_.find(id);
|
auto it = modules_.find(id);
|
||||||
if (it == modules_.end()) {
|
if (it == modules_.end()) {
|
||||||
E2D_LOG_WARN("Module with id {} not found for unregistration", id);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,7 +71,6 @@ bool ModuleRegistry::unregisterModule(ModuleId id) {
|
||||||
nameToId_.erase(info.name);
|
nameToId_.erase(info.name);
|
||||||
modules_.erase(it);
|
modules_.erase(it);
|
||||||
|
|
||||||
E2D_LOG_INFO("Unregistered module '{}' (id: {})", info.name, id);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,11 +110,11 @@ IModuleConfig* ModuleRegistry::getModuleConfigByName(const std::string& name) co
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 创建模块初始化器
|
* @brief 获取或创建模块初始化器
|
||||||
* @param id 模块标识符
|
* @param id 模块标识符
|
||||||
* @return 初始化器实例,不存在返回 nullptr
|
* @return 初始化器指针,不存在返回 nullptr
|
||||||
*/
|
*/
|
||||||
UniquePtr<IModuleInitializer> ModuleRegistry::createInitializer(ModuleId id) const {
|
IModuleInitializer* ModuleRegistry::getInitializer(ModuleId id) {
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
||||||
auto it = modules_.find(id);
|
auto it = modules_.find(id);
|
||||||
|
|
@ -125,7 +122,11 @@ UniquePtr<IModuleInitializer> ModuleRegistry::createInitializer(ModuleId id) con
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return it->second.initializerFactory();
|
if (!it->second.initializer) {
|
||||||
|
it->second.initializer = it->second.initializerFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
return it->second.initializer.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -197,8 +198,6 @@ void ModuleRegistry::clear() {
|
||||||
modules_.clear();
|
modules_.clear();
|
||||||
nameToId_.clear();
|
nameToId_.clear();
|
||||||
nextId_ = 1;
|
nextId_ = 1;
|
||||||
|
|
||||||
E2D_LOG_INFO("Module registry cleared");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -9,85 +9,47 @@ using json = nlohmann::json;
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
// ============================================================================
|
static ModuleId s_renderModuleId = INVALID_MODULE_ID;
|
||||||
// RenderModuleConfig 实现
|
|
||||||
// ============================================================================
|
ModuleId get_render_module_id() {
|
||||||
|
return s_renderModuleId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 验证渲染配置有效性
|
|
||||||
*
|
|
||||||
* 检查渲染配置的各项参数是否在有效范围内:
|
|
||||||
* - 目标帧率应在 1-240 之间
|
|
||||||
* - 多重采样数应为 0、2、4、8 或 16
|
|
||||||
* - 精灵批处理大小应大于 0
|
|
||||||
*
|
|
||||||
* @return 如果配置有效返回 true
|
|
||||||
*/
|
|
||||||
bool RenderModuleConfig::validate() const {
|
bool RenderModuleConfig::validate() const {
|
||||||
if (targetFPS < 1 || targetFPS > 240) {
|
if (targetFPS < 1 || targetFPS > 240) {
|
||||||
E2D_LOG_ERROR("Invalid target FPS: {}, must be between 1 and 240", targetFPS);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (multisamples != 0 && multisamples != 2 && multisamples != 4 &&
|
if (multisamples != 0 && multisamples != 2 && multisamples != 4 &&
|
||||||
multisamples != 8 && multisamples != 16) {
|
multisamples != 8 && multisamples != 16) {
|
||||||
E2D_LOG_ERROR("Invalid multisample count: {}, must be 0, 2, 4, 8 or 16", multisamples);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spriteBatchSize <= 0) {
|
if (spriteBatchSize <= 0) {
|
||||||
E2D_LOG_ERROR("Invalid sprite batch size: {}, must be greater than 0", spriteBatchSize);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 应用平台约束
|
|
||||||
*
|
|
||||||
* 根据不同平台的特性调整渲染配置:
|
|
||||||
* - Switch 平台限制 MSAA 最大为 4,禁用 sRGB 帧缓冲
|
|
||||||
* - 其他平台保持用户配置
|
|
||||||
*
|
|
||||||
* @param platform 目标平台类型
|
|
||||||
*/
|
|
||||||
void RenderModuleConfig::applyPlatformConstraints(PlatformType platform) {
|
void RenderModuleConfig::applyPlatformConstraints(PlatformType platform) {
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
case PlatformType::Switch:
|
case PlatformType::Switch:
|
||||||
if (multisamples > 4) {
|
if (multisamples > 4) {
|
||||||
E2D_LOG_WARN("Switch platform limits MSAA to 4x, reducing from {}", multisamples);
|
|
||||||
multisamples = 4;
|
multisamples = 4;
|
||||||
}
|
}
|
||||||
if (sRGBFramebuffer) {
|
if (sRGBFramebuffer) {
|
||||||
E2D_LOG_WARN("Switch platform does not support sRGB framebuffer, disabling");
|
|
||||||
sRGBFramebuffer = false;
|
sRGBFramebuffer = false;
|
||||||
}
|
}
|
||||||
if (targetFPS > 60) {
|
if (targetFPS > 60) {
|
||||||
E2D_LOG_WARN("Switch platform target FPS capped at 60");
|
|
||||||
targetFPS = 60;
|
targetFPS = 60;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PlatformType::Windows:
|
|
||||||
case PlatformType::Linux:
|
|
||||||
case PlatformType::macOS:
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 重置为默认配置
|
|
||||||
*
|
|
||||||
* 将所有配置项恢复为默认值:
|
|
||||||
* - 后端类型:OpenGL
|
|
||||||
* - 垂直同步:启用
|
|
||||||
* - 目标帧率:60
|
|
||||||
* - 多重采样:禁用
|
|
||||||
* - sRGB 帧缓冲:禁用
|
|
||||||
* - 精灵批处理大小:1000
|
|
||||||
*/
|
|
||||||
void RenderModuleConfig::resetToDefaults() {
|
void RenderModuleConfig::resetToDefaults() {
|
||||||
backend = BackendType::OpenGL;
|
backend = BackendType::OpenGL;
|
||||||
vsync = true;
|
vsync = true;
|
||||||
|
|
@ -97,19 +59,8 @@ void RenderModuleConfig::resetToDefaults() {
|
||||||
spriteBatchSize = 1000;
|
spriteBatchSize = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从 JSON 数据加载配置
|
|
||||||
*
|
|
||||||
* 从 JSON 对象中解析渲染配置参数
|
|
||||||
*
|
|
||||||
* @param jsonData JSON 数据指针(nlohmann::json 对象指针)
|
|
||||||
* @return 加载成功返回 true
|
|
||||||
*/
|
|
||||||
bool RenderModuleConfig::loadFromJson(const void* jsonData) {
|
bool RenderModuleConfig::loadFromJson(const void* jsonData) {
|
||||||
if (!jsonData) {
|
if (!jsonData) return false;
|
||||||
E2D_LOG_ERROR("Null JSON data provided");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const json& j = *static_cast<const json*>(jsonData);
|
const json& j = *static_cast<const json*>(jsonData);
|
||||||
|
|
@ -118,9 +69,6 @@ bool RenderModuleConfig::loadFromJson(const void* jsonData) {
|
||||||
std::string backendStr = j["backend"].get<std::string>();
|
std::string backendStr = j["backend"].get<std::string>();
|
||||||
if (backendStr == "opengl") {
|
if (backendStr == "opengl") {
|
||||||
backend = BackendType::OpenGL;
|
backend = BackendType::OpenGL;
|
||||||
} else {
|
|
||||||
E2D_LOG_WARN("Unknown backend type: {}, defaulting to OpenGL", backendStr);
|
|
||||||
backend = BackendType::OpenGL;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,124 +92,57 @@ bool RenderModuleConfig::loadFromJson(const void* jsonData) {
|
||||||
spriteBatchSize = j["spriteBatchSize"].get<int>();
|
spriteBatchSize = j["spriteBatchSize"].get<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
E2D_LOG_INFO("Render config loaded from JSON");
|
|
||||||
return true;
|
return true;
|
||||||
} catch (const json::exception& e) {
|
} catch (...) {
|
||||||
E2D_LOG_ERROR("Failed to parse render config from JSON: {}", e.what());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 保存配置到 JSON 数据
|
|
||||||
*
|
|
||||||
* 将当前配置序列化到 JSON 对象
|
|
||||||
*
|
|
||||||
* @param jsonData JSON 数据指针(nlohmann::json 对象指针)
|
|
||||||
* @return 保存成功返回 true
|
|
||||||
*/
|
|
||||||
bool RenderModuleConfig::saveToJson(void* jsonData) const {
|
bool RenderModuleConfig::saveToJson(void* jsonData) const {
|
||||||
if (!jsonData) {
|
if (!jsonData) return false;
|
||||||
E2D_LOG_ERROR("Null JSON data provided");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
json& j = *static_cast<json*>(jsonData);
|
json& j = *static_cast<json*>(jsonData);
|
||||||
|
j["backend"] = "opengl";
|
||||||
std::string backendStr = "opengl";
|
|
||||||
switch (backend) {
|
|
||||||
case BackendType::OpenGL:
|
|
||||||
backendStr = "opengl";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
backendStr = "opengl";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
j["backend"] = backendStr;
|
|
||||||
j["vsync"] = vsync;
|
j["vsync"] = vsync;
|
||||||
j["targetFPS"] = targetFPS;
|
j["targetFPS"] = targetFPS;
|
||||||
j["multisamples"] = multisamples;
|
j["multisamples"] = multisamples;
|
||||||
j["sRGBFramebuffer"] = sRGBFramebuffer;
|
j["sRGBFramebuffer"] = sRGBFramebuffer;
|
||||||
j["spriteBatchSize"] = spriteBatchSize;
|
j["spriteBatchSize"] = spriteBatchSize;
|
||||||
|
|
||||||
E2D_LOG_INFO("Render config saved to JSON");
|
|
||||||
return true;
|
return true;
|
||||||
} catch (const json::exception& e) {
|
} catch (...) {
|
||||||
E2D_LOG_ERROR("Failed to save render config to JSON: {}", e.what());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// RenderModuleInitializer 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
*
|
|
||||||
* 初始化渲染模块初始化器的成员变量
|
|
||||||
*/
|
|
||||||
RenderModuleInitializer::RenderModuleInitializer()
|
RenderModuleInitializer::RenderModuleInitializer()
|
||||||
: moduleId_(INVALID_MODULE_ID)
|
: moduleId_(INVALID_MODULE_ID)
|
||||||
, windowModuleId_(INVALID_MODULE_ID)
|
, window_(nullptr)
|
||||||
, renderer_(nullptr)
|
|
||||||
, initialized_(false) {
|
, initialized_(false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*
|
|
||||||
* 确保在销毁时关闭模块
|
|
||||||
*/
|
|
||||||
RenderModuleInitializer::~RenderModuleInitializer() {
|
RenderModuleInitializer::~RenderModuleInitializer() {
|
||||||
if (initialized_) {
|
if (initialized_) {
|
||||||
shutdown();
|
shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取模块依赖列表
|
|
||||||
*
|
|
||||||
* 返回渲染模块依赖的窗口模块标识符列表
|
|
||||||
*
|
|
||||||
* @return 依赖模块标识符列表
|
|
||||||
*/
|
|
||||||
std::vector<ModuleId> RenderModuleInitializer::getDependencies() const {
|
std::vector<ModuleId> RenderModuleInitializer::getDependencies() const {
|
||||||
if (windowModuleId_ != INVALID_MODULE_ID) {
|
|
||||||
return {windowModuleId_};
|
|
||||||
}
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化模块
|
|
||||||
*
|
|
||||||
* 根据配置创建渲染后端实例并初始化渲染器
|
|
||||||
*
|
|
||||||
* @param config 模块配置指针
|
|
||||||
* @return 初始化成功返回 true
|
|
||||||
*/
|
|
||||||
bool RenderModuleInitializer::initialize(const IModuleConfig* config) {
|
bool RenderModuleInitializer::initialize(const IModuleConfig* config) {
|
||||||
if (initialized_) {
|
if (initialized_) return true;
|
||||||
E2D_LOG_WARN("Render module already initialized");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config) {
|
if (!config) return false;
|
||||||
E2D_LOG_ERROR("Null config provided for render module initialization");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RenderModuleConfig* renderConfig = dynamic_cast<const RenderModuleConfig*>(config);
|
const RenderModuleConfig* renderConfig = dynamic_cast<const RenderModuleConfig*>(config);
|
||||||
if (!renderConfig) {
|
if (!renderConfig) return false;
|
||||||
E2D_LOG_ERROR("Invalid config type for render module");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!renderConfig->validate()) {
|
if (!renderConfig->validate()) return false;
|
||||||
E2D_LOG_ERROR("Invalid render module configuration");
|
|
||||||
|
if (!window_) {
|
||||||
|
E2D_LOG_ERROR("Render module requires window to be set");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -271,35 +152,19 @@ bool RenderModuleInitializer::initialize(const IModuleConfig* config) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
IWindow* window = nullptr;
|
if (!renderer_->init(window_)) {
|
||||||
if (windowModuleId_ != INVALID_MODULE_ID) {
|
E2D_LOG_ERROR("Failed to initialize renderer");
|
||||||
ModuleRegistry& registry = ModuleRegistry::instance();
|
renderer_.reset();
|
||||||
IModuleConfig* windowConfig = registry.getModuleConfig(windowModuleId_);
|
return false;
|
||||||
if (windowConfig) {
|
|
||||||
E2D_LOG_INFO("Render module found window module dependency");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
E2D_LOG_INFO("Render module initialized successfully");
|
|
||||||
E2D_LOG_INFO(" Backend: {}", renderConfig->backend == BackendType::OpenGL ? "OpenGL" : "Unknown");
|
|
||||||
E2D_LOG_INFO(" VSync: {}", renderConfig->vsync ? "enabled" : "disabled");
|
|
||||||
E2D_LOG_INFO(" Target FPS: {}", renderConfig->targetFPS);
|
|
||||||
E2D_LOG_INFO(" Multisamples: {}", renderConfig->multisamples);
|
|
||||||
E2D_LOG_INFO(" Sprite Batch Size: {}", renderConfig->spriteBatchSize);
|
|
||||||
|
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
|
E2D_LOG_INFO("Render module initialized");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭模块
|
|
||||||
*
|
|
||||||
* 销毁渲染后端实例并清理资源
|
|
||||||
*/
|
|
||||||
void RenderModuleInitializer::shutdown() {
|
void RenderModuleInitializer::shutdown() {
|
||||||
if (!initialized_) {
|
if (!initialized_) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (renderer_) {
|
if (renderer_) {
|
||||||
renderer_->shutdown();
|
renderer_->shutdown();
|
||||||
|
|
@ -307,7 +172,30 @@ void RenderModuleInitializer::shutdown() {
|
||||||
}
|
}
|
||||||
|
|
||||||
initialized_ = false;
|
initialized_ = false;
|
||||||
E2D_LOG_INFO("Render module shutdown complete");
|
E2D_LOG_INFO("Render module shutdown");
|
||||||
|
}
|
||||||
|
|
||||||
|
void register_render_module() {
|
||||||
|
if (s_renderModuleId != INVALID_MODULE_ID) return;
|
||||||
|
|
||||||
|
s_renderModuleId = ModuleRegistry::instance().registerModule(
|
||||||
|
makeUnique<RenderModuleConfig>(),
|
||||||
|
[]() -> UniquePtr<IModuleInitializer> {
|
||||||
|
auto initializer = makeUnique<RenderModuleInitializer>();
|
||||||
|
initializer->setModuleId(s_renderModuleId);
|
||||||
|
return initializer;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct RenderModuleAutoRegister {
|
||||||
|
RenderModuleAutoRegister() {
|
||||||
|
register_render_module();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static RenderModuleAutoRegister s_autoRegister;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
#include <extra2d/platform/platform_init_module.h>
|
||||||
|
#include <extra2d/config/module_registry.h>
|
||||||
|
#include <extra2d/config/config_manager.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
#include <switch.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
static ModuleId s_platformModuleId = INVALID_MODULE_ID;
|
||||||
|
|
||||||
|
ModuleId get_platform_module_id() {
|
||||||
|
return s_platformModuleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlatformModuleConfig::loadFromJson(const void* jsonData) {
|
||||||
|
if (!jsonData) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const json& j = *static_cast<const json*>(jsonData);
|
||||||
|
|
||||||
|
if (j.contains("targetPlatform")) {
|
||||||
|
int platform = j["targetPlatform"].get<int>();
|
||||||
|
if (platform >= 0 && platform <= 5) {
|
||||||
|
targetPlatform = static_cast<PlatformType>(platform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlatformModuleConfig::saveToJson(void* jsonData) const {
|
||||||
|
if (!jsonData) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
json& j = *static_cast<json*>(jsonData);
|
||||||
|
j["targetPlatform"] = static_cast<int>(targetPlatform);
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlatformModuleInitializer::PlatformModuleInitializer()
|
||||||
|
: moduleId_(INVALID_MODULE_ID)
|
||||||
|
, initialized_(false)
|
||||||
|
, targetPlatform_(PlatformType::Auto)
|
||||||
|
, resolvedPlatform_(PlatformType::Windows) {
|
||||||
|
}
|
||||||
|
|
||||||
|
PlatformModuleInitializer::~PlatformModuleInitializer() {
|
||||||
|
if (initialized_) {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlatformModuleInitializer::initialize(const IModuleConfig* config) {
|
||||||
|
if (initialized_) return true;
|
||||||
|
|
||||||
|
const PlatformModuleConfig* platformConfig = dynamic_cast<const PlatformModuleConfig*>(config);
|
||||||
|
if (platformConfig) {
|
||||||
|
targetPlatform_ = platformConfig->targetPlatform;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvedPlatform_ = targetPlatform_;
|
||||||
|
if (resolvedPlatform_ == PlatformType::Auto) {
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
resolvedPlatform_ = PlatformType::Switch;
|
||||||
|
#else
|
||||||
|
#ifdef _WIN32
|
||||||
|
resolvedPlatform_ = PlatformType::Windows;
|
||||||
|
#elif defined(__linux__)
|
||||||
|
resolvedPlatform_ = PlatformType::Linux;
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
resolvedPlatform_ = PlatformType::macOS;
|
||||||
|
#else
|
||||||
|
resolvedPlatform_ = PlatformType::Windows;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
platformConfig_ = createPlatformConfig(resolvedPlatform_);
|
||||||
|
if (!platformConfig_) {
|
||||||
|
E2D_LOG_ERROR("Failed to create platform config");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& appConfig = ConfigManager::instance().appConfig();
|
||||||
|
appConfig.applyPlatformConstraints(*platformConfig_);
|
||||||
|
|
||||||
|
if (resolvedPlatform_ == PlatformType::Switch) {
|
||||||
|
if (!initSwitch()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initialized_ = true;
|
||||||
|
E2D_LOG_INFO("Platform module initialized ({})", getPlatformTypeName(resolvedPlatform_));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlatformModuleInitializer::initSwitch() {
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
Result rc;
|
||||||
|
rc = romfsInit();
|
||||||
|
if (R_SUCCEEDED(rc)) {
|
||||||
|
E2D_LOG_INFO("RomFS initialized successfully");
|
||||||
|
} else {
|
||||||
|
E2D_LOG_WARN("romfsInit failed: {:#08X}, will use regular filesystem", rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = socketInitializeDefault();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
E2D_LOG_WARN("socketInitializeDefault failed, nxlink will not be available");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlatformModuleInitializer::shutdown() {
|
||||||
|
if (!initialized_) return;
|
||||||
|
|
||||||
|
E2D_LOG_INFO("Platform module shutting down");
|
||||||
|
|
||||||
|
if (resolvedPlatform_ == PlatformType::Switch) {
|
||||||
|
shutdownSwitch();
|
||||||
|
}
|
||||||
|
|
||||||
|
platformConfig_.reset();
|
||||||
|
initialized_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlatformModuleInitializer::shutdownSwitch() {
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
romfsExit();
|
||||||
|
socketExit();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void register_platform_module() {
|
||||||
|
if (s_platformModuleId != INVALID_MODULE_ID) return;
|
||||||
|
|
||||||
|
s_platformModuleId = ModuleRegistry::instance().registerModule(
|
||||||
|
makeUnique<PlatformModuleConfig>(),
|
||||||
|
[]() -> UniquePtr<IModuleInitializer> {
|
||||||
|
auto initializer = makeUnique<PlatformModuleInitializer>();
|
||||||
|
initializer->setModuleId(s_platformModuleId);
|
||||||
|
return initializer;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct PlatformModuleAutoRegister {
|
||||||
|
PlatformModuleAutoRegister() {
|
||||||
|
register_platform_module();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static PlatformModuleAutoRegister s_autoRegister;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,340 +1,249 @@
|
||||||
#include <extra2d/platform/window_module.h>
|
#include <extra2d/platform/window_module.h>
|
||||||
#include <extra2d/config/module_registry.h>
|
#include <extra2d/config/module_registry.h>
|
||||||
|
#include <extra2d/config/config_manager.h>
|
||||||
#include <extra2d/platform/platform_module.h>
|
#include <extra2d/platform/platform_module.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#ifdef E2D_BACKEND_SDL2
|
||||||
|
#include <SDL.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef E2D_BACKEND_GLFW
|
||||||
|
#include <GLFW/glfw3.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
#include <switch.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
// ============================================================================
|
static ModuleId s_windowModuleId = INVALID_MODULE_ID;
|
||||||
// WindowModuleConfig 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
ModuleId get_window_module_id() {
|
||||||
* @brief 验证窗口配置有效性
|
return s_windowModuleId;
|
||||||
* 检查窗口尺寸、标题等配置是否合法
|
|
||||||
* @return 如果配置有效返回 true
|
|
||||||
*/
|
|
||||||
bool WindowModuleConfig::validate() const {
|
|
||||||
if (windowConfig.width <= 0) {
|
|
||||||
E2D_LOG_ERROR("Window width must be positive, got: {}", windowConfig.width);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (windowConfig.height <= 0) {
|
|
||||||
E2D_LOG_ERROR("Window height must be positive, got: {}", windowConfig.height);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (windowConfig.title.empty()) {
|
|
||||||
E2D_LOG_WARN("Window title is empty, using default title");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (windowConfig.multisamples < 0) {
|
|
||||||
E2D_LOG_ERROR("MSAA samples cannot be negative, got: {}", windowConfig.multisamples);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (windowConfig.multisamples != 0 &&
|
|
||||||
windowConfig.multisamples != 2 &&
|
|
||||||
windowConfig.multisamples != 4 &&
|
|
||||||
windowConfig.multisamples != 8 &&
|
|
||||||
windowConfig.multisamples != 16) {
|
|
||||||
E2D_LOG_WARN("MSAA samples should be 0, 2, 4, 8, or 16, got: {}", windowConfig.multisamples);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (backend.empty()) {
|
|
||||||
E2D_LOG_ERROR("Backend name cannot be empty");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 应用平台约束
|
|
||||||
* 根据目标平台特性调整窗口配置
|
|
||||||
* @param platform 目标平台类型
|
|
||||||
*/
|
|
||||||
void WindowModuleConfig::applyPlatformConstraints(PlatformType platform) {
|
|
||||||
switch (platform) {
|
|
||||||
case PlatformType::Switch:
|
|
||||||
E2D_LOG_INFO("Applying Nintendo Switch platform constraints");
|
|
||||||
windowConfig.mode = WindowMode::Fullscreen;
|
|
||||||
windowConfig.resizable = false;
|
|
||||||
windowConfig.centered = false;
|
|
||||||
windowConfig.width = 1920;
|
|
||||||
windowConfig.height = 1080;
|
|
||||||
backend = "switch";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PlatformType::Windows:
|
|
||||||
case PlatformType::Linux:
|
|
||||||
case PlatformType::macOS:
|
|
||||||
E2D_LOG_INFO("Applying desktop platform constraints");
|
|
||||||
if (windowConfig.width <= 0) {
|
|
||||||
windowConfig.width = 1280;
|
|
||||||
}
|
|
||||||
if (windowConfig.height <= 0) {
|
|
||||||
windowConfig.height = 720;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PlatformType::Auto:
|
|
||||||
default:
|
|
||||||
E2D_LOG_INFO("Auto-detecting platform constraints");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 重置为默认配置
|
|
||||||
* 将所有配置项恢复为默认值
|
|
||||||
*/
|
|
||||||
void WindowModuleConfig::resetToDefaults() {
|
|
||||||
windowConfig = WindowConfigData{};
|
|
||||||
backend = "sdl2";
|
|
||||||
|
|
||||||
E2D_LOG_INFO("Window module config reset to defaults");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从 JSON 数据加载配置
|
|
||||||
* 解析 JSON 对象并填充配置数据
|
|
||||||
* @param jsonData JSON 数据指针
|
|
||||||
* @return 加载成功返回 true
|
|
||||||
*/
|
|
||||||
bool WindowModuleConfig::loadFromJson(const void* jsonData) {
|
bool WindowModuleConfig::loadFromJson(const void* jsonData) {
|
||||||
if (!jsonData) {
|
if (!jsonData) return false;
|
||||||
E2D_LOG_ERROR("JSON data is null");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const json& obj = *static_cast<const json*>(jsonData);
|
try {
|
||||||
|
const json& j = *static_cast<const json*>(jsonData);
|
||||||
if (!obj.is_object()) {
|
|
||||||
E2D_LOG_ERROR("JSON data must be an object");
|
if (j.contains("backend")) {
|
||||||
return false;
|
backend = j["backend"].get<std::string>();
|
||||||
}
|
|
||||||
|
|
||||||
if (obj.contains("title") && obj["title"].is_string()) {
|
|
||||||
windowConfig.title = obj["title"].get<std::string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj.contains("width") && obj["width"].is_number_integer()) {
|
|
||||||
windowConfig.width = obj["width"].get<int>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj.contains("height") && obj["height"].is_number_integer()) {
|
|
||||||
windowConfig.height = obj["height"].get<int>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj.contains("fullscreen") && obj["fullscreen"].is_boolean()) {
|
|
||||||
windowConfig.mode = obj["fullscreen"].get<bool>() ? WindowMode::Fullscreen : WindowMode::Windowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj.contains("mode") && obj["mode"].is_string()) {
|
|
||||||
std::string modeStr = obj["mode"].get<std::string>();
|
|
||||||
if (modeStr == "fullscreen") {
|
|
||||||
windowConfig.mode = WindowMode::Fullscreen;
|
|
||||||
} else if (modeStr == "borderless") {
|
|
||||||
windowConfig.mode = WindowMode::Borderless;
|
|
||||||
} else {
|
|
||||||
windowConfig.mode = WindowMode::Windowed;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (j.contains("title")) {
|
||||||
if (obj.contains("resizable") && obj["resizable"].is_boolean()) {
|
windowConfig.title = j["title"].get<std::string>();
|
||||||
windowConfig.resizable = obj["resizable"].get<bool>();
|
}
|
||||||
}
|
if (j.contains("width")) {
|
||||||
|
windowConfig.width = j["width"].get<int>();
|
||||||
if (obj.contains("vsync") && obj["vsync"].is_boolean()) {
|
}
|
||||||
windowConfig.vsync = obj["vsync"].get<bool>();
|
if (j.contains("height")) {
|
||||||
}
|
windowConfig.height = j["height"].get<int>();
|
||||||
|
}
|
||||||
if (obj.contains("multisamples") && obj["multisamples"].is_number_integer()) {
|
if (j.contains("fullscreen")) {
|
||||||
windowConfig.multisamples = obj["multisamples"].get<int>();
|
windowConfig.mode = j["fullscreen"].get<bool>() ? WindowMode::Fullscreen : WindowMode::Windowed;
|
||||||
}
|
}
|
||||||
|
if (j.contains("vsync")) {
|
||||||
if (obj.contains("msaaSamples") && obj["msaaSamples"].is_number_integer()) {
|
windowConfig.vsync = j["vsync"].get<bool>();
|
||||||
windowConfig.multisamples = obj["msaaSamples"].get<int>();
|
}
|
||||||
}
|
if (j.contains("resizable")) {
|
||||||
|
windowConfig.resizable = j["resizable"].get<bool>();
|
||||||
if (obj.contains("centered") && obj["centered"].is_boolean()) {
|
}
|
||||||
windowConfig.centered = obj["centered"].get<bool>();
|
|
||||||
}
|
return true;
|
||||||
|
} catch (...) {
|
||||||
if (obj.contains("centerWindow") && obj["centerWindow"].is_boolean()) {
|
|
||||||
windowConfig.centered = obj["centerWindow"].get<bool>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj.contains("visible") && obj["visible"].is_boolean()) {
|
|
||||||
windowConfig.visible = obj["visible"].get<bool>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj.contains("decorated") && obj["decorated"].is_boolean()) {
|
|
||||||
windowConfig.decorated = obj["decorated"].get<bool>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj.contains("backend") && obj["backend"].is_string()) {
|
|
||||||
backend = obj["backend"].get<std::string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_LOG_INFO("Window module config loaded from JSON");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 保存配置到 JSON 数据
|
|
||||||
* 将配置数据序列化为 JSON 对象
|
|
||||||
* @param jsonData JSON 数据指针
|
|
||||||
* @return 保存成功返回 true
|
|
||||||
*/
|
|
||||||
bool WindowModuleConfig::saveToJson(void* jsonData) const {
|
|
||||||
if (!jsonData) {
|
|
||||||
E2D_LOG_ERROR("JSON data pointer is null");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
json& obj = *static_cast<json*>(jsonData);
|
|
||||||
|
|
||||||
obj["title"] = windowConfig.title;
|
|
||||||
obj["width"] = windowConfig.width;
|
|
||||||
obj["height"] = windowConfig.height;
|
|
||||||
|
|
||||||
std::string modeStr = "windowed";
|
|
||||||
if (windowConfig.mode == WindowMode::Fullscreen) {
|
|
||||||
modeStr = "fullscreen";
|
|
||||||
} else if (windowConfig.mode == WindowMode::Borderless) {
|
|
||||||
modeStr = "borderless";
|
|
||||||
}
|
|
||||||
obj["mode"] = modeStr;
|
|
||||||
|
|
||||||
obj["resizable"] = windowConfig.resizable;
|
|
||||||
obj["vsync"] = windowConfig.vsync;
|
|
||||||
obj["multisamples"] = windowConfig.multisamples;
|
|
||||||
obj["centered"] = windowConfig.centered;
|
|
||||||
obj["visible"] = windowConfig.visible;
|
|
||||||
obj["decorated"] = windowConfig.decorated;
|
|
||||||
obj["backend"] = backend;
|
|
||||||
|
|
||||||
E2D_LOG_INFO("Window module config saved to JSON");
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
bool WindowModuleConfig::saveToJson(void* jsonData) const {
|
||||||
// WindowModuleInitializer 实现
|
if (!jsonData) return false;
|
||||||
// ============================================================================
|
|
||||||
|
try {
|
||||||
|
json& j = *static_cast<json*>(jsonData);
|
||||||
|
j["backend"] = backend;
|
||||||
|
j["title"] = windowConfig.title;
|
||||||
|
j["width"] = windowConfig.width;
|
||||||
|
j["height"] = windowConfig.height;
|
||||||
|
j["fullscreen"] = (windowConfig.mode == WindowMode::Fullscreen);
|
||||||
|
j["vsync"] = windowConfig.vsync;
|
||||||
|
j["resizable"] = windowConfig.resizable;
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
* 初始化窗口模块初始化器
|
|
||||||
*/
|
|
||||||
WindowModuleInitializer::WindowModuleInitializer()
|
WindowModuleInitializer::WindowModuleInitializer()
|
||||||
: moduleId_(INVALID_MODULE_ID)
|
: moduleId_(INVALID_MODULE_ID)
|
||||||
, window_(nullptr)
|
, initialized_(false)
|
||||||
, initialized_(false) {
|
, backendInitialized_(false) {
|
||||||
E2D_LOG_DEBUG("WindowModuleInitializer constructed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
* 确保模块正确关闭
|
|
||||||
*/
|
|
||||||
WindowModuleInitializer::~WindowModuleInitializer() {
|
WindowModuleInitializer::~WindowModuleInitializer() {
|
||||||
if (initialized_) {
|
if (initialized_) {
|
||||||
shutdown();
|
shutdown();
|
||||||
}
|
}
|
||||||
E2D_LOG_DEBUG("WindowModuleInitializer destructed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
bool WindowModuleInitializer::initBackend() {
|
||||||
* @brief 初始化模块
|
#ifdef E2D_BACKEND_SDL2
|
||||||
* 使用 BackendFactory 创建窗口实例
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER) != 0) {
|
||||||
* @param config 模块配置指针
|
E2D_LOG_ERROR("Failed to initialize SDL2: {}", SDL_GetError());
|
||||||
* @return 初始化成功返回 true
|
|
||||||
*/
|
|
||||||
bool WindowModuleInitializer::initialize(const IModuleConfig* config) {
|
|
||||||
if (initialized_) {
|
|
||||||
E2D_LOG_WARN("Window module already initialized");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config) {
|
|
||||||
E2D_LOG_ERROR("Window module config is null");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
E2D_LOG_INFO("SDL2 backend initialized");
|
||||||
|
backendInitialized_ = true;
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef E2D_BACKEND_GLFW
|
||||||
|
if (!glfwInit()) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize GLFW");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
E2D_LOG_INFO("GLFW backend initialized");
|
||||||
|
backendInitialized_ = true;
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef E2D_BACKEND_SWITCH
|
||||||
|
E2D_LOG_INFO("Switch backend (no init required)");
|
||||||
|
backendInitialized_ = true;
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
E2D_LOG_ERROR("No backend available");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowModuleInitializer::shutdownBackend() {
|
||||||
|
if (!backendInitialized_) return;
|
||||||
|
|
||||||
|
#ifdef E2D_BACKEND_SDL2
|
||||||
|
SDL_Quit();
|
||||||
|
E2D_LOG_INFO("SDL2 backend shutdown");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef E2D_BACKEND_GLFW
|
||||||
|
glfwTerminate();
|
||||||
|
E2D_LOG_INFO("GLFW backend shutdown");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
backendInitialized_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowModuleInitializer::initialize(const IModuleConfig* config) {
|
||||||
|
if (initialized_) return true;
|
||||||
|
|
||||||
const WindowModuleConfig* windowConfig = dynamic_cast<const WindowModuleConfig*>(config);
|
const WindowModuleConfig* windowConfig = dynamic_cast<const WindowModuleConfig*>(config);
|
||||||
if (!windowConfig) {
|
if (!windowConfig) {
|
||||||
E2D_LOG_ERROR("Invalid config type for window module");
|
E2D_LOG_ERROR("Invalid window module config");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
backend_ = windowConfig->backend;
|
||||||
|
windowConfig_ = windowConfig->windowConfig;
|
||||||
|
|
||||||
ModuleInfo info = config->getModuleInfo();
|
#ifdef __SWITCH__
|
||||||
moduleId_ = info.id;
|
backend_ = "switch";
|
||||||
|
windowConfig_.mode = WindowMode::Fullscreen;
|
||||||
const std::string& backend = windowConfig->backend;
|
windowConfig_.resizable = false;
|
||||||
|
#endif
|
||||||
if (!BackendFactory::has(backend)) {
|
|
||||||
E2D_LOG_ERROR("Backend '{}' not available", backend);
|
if (!initBackend()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef E2D_BACKEND_SDL2
|
||||||
|
extern void initSDL2Backend();
|
||||||
|
initSDL2Backend();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!BackendFactory::has(backend_)) {
|
||||||
|
E2D_LOG_ERROR("Backend '{}' not available", backend_);
|
||||||
auto backends = BackendFactory::backends();
|
auto backends = BackendFactory::backends();
|
||||||
if (backends.empty()) {
|
if (backends.empty()) {
|
||||||
E2D_LOG_ERROR("No backends registered!");
|
E2D_LOG_ERROR("No backends registered!");
|
||||||
|
shutdownBackend();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
std::string backendList;
|
backend_ = backends[0];
|
||||||
for (const auto& b : backends) {
|
E2D_LOG_WARN("Using fallback backend: {}", backend_);
|
||||||
if (!backendList.empty()) backendList += ", ";
|
}
|
||||||
backendList += b;
|
|
||||||
}
|
if (!createWindow(backend_, windowConfig_)) {
|
||||||
E2D_LOG_WARN("Available backends: {}", backendList);
|
E2D_LOG_ERROR("Failed to create window");
|
||||||
|
shutdownBackend();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initialized_ = true;
|
||||||
|
E2D_LOG_INFO("Window module initialized");
|
||||||
|
E2D_LOG_INFO(" Window: {}x{}", window_->width(), window_->height());
|
||||||
|
E2D_LOG_INFO(" Backend: {}", backend_);
|
||||||
|
E2D_LOG_INFO(" VSync: {}", windowConfig_.vsync);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowModuleInitializer::createWindow(const std::string& backend, const WindowConfigData& config) {
|
||||||
window_ = BackendFactory::createWindow(backend);
|
window_ = BackendFactory::createWindow(backend);
|
||||||
if (!window_) {
|
if (!window_) {
|
||||||
E2D_LOG_ERROR("Failed to create window for backend: {}", backend);
|
E2D_LOG_ERROR("Failed to create window for backend: {}", backend);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!window_->create(windowConfig->windowConfig)) {
|
if (!window_->create(config)) {
|
||||||
E2D_LOG_ERROR("Failed to create window with given config");
|
E2D_LOG_ERROR("Failed to create window");
|
||||||
window_.reset();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
initialized_ = true;
|
|
||||||
E2D_LOG_INFO("Window module initialized successfully (backend: {}, {}x{})",
|
|
||||||
backend,
|
|
||||||
windowConfig->windowConfig.width,
|
|
||||||
windowConfig->windowConfig.height);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭模块
|
|
||||||
* 销毁窗口实例并重置状态
|
|
||||||
*/
|
|
||||||
void WindowModuleInitializer::shutdown() {
|
void WindowModuleInitializer::shutdown() {
|
||||||
if (!initialized_) {
|
if (!initialized_) return;
|
||||||
E2D_LOG_WARN("Window module not initialized, nothing to shutdown");
|
|
||||||
return;
|
E2D_LOG_INFO("Window module shutting down");
|
||||||
}
|
|
||||||
|
|
||||||
if (window_) {
|
if (window_) {
|
||||||
window_->destroy();
|
window_->destroy();
|
||||||
window_.reset();
|
window_.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
initialized_ = false;
|
shutdownBackend();
|
||||||
moduleId_ = INVALID_MODULE_ID;
|
|
||||||
|
|
||||||
E2D_LOG_INFO("Window module shutdown complete");
|
initialized_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void register_window_module() {
|
||||||
|
if (s_windowModuleId != INVALID_MODULE_ID) return;
|
||||||
|
|
||||||
|
s_windowModuleId = ModuleRegistry::instance().registerModule(
|
||||||
|
makeUnique<WindowModuleConfig>(),
|
||||||
|
[]() -> UniquePtr<IModuleInitializer> {
|
||||||
|
auto initializer = makeUnique<WindowModuleInitializer>();
|
||||||
|
initializer->setModuleId(s_windowModuleId);
|
||||||
|
return initializer;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct WindowModuleAutoRegister {
|
||||||
|
WindowModuleAutoRegister() {
|
||||||
|
register_window_module();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static WindowModuleAutoRegister s_autoRegister;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,114 +1,177 @@
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
#include <switch.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
// 静态成员定义
|
|
||||||
LogLevel Logger::level_ = LogLevel::Info;
|
LogLevel Logger::level_ = LogLevel::Info;
|
||||||
bool Logger::initialized_ = false;
|
bool Logger::initialized_ = false;
|
||||||
bool Logger::consoleOutput_ = true;
|
bool Logger::consoleOutput_ = true;
|
||||||
bool Logger::fileOutput_ = false;
|
bool Logger::fileOutput_ = false;
|
||||||
std::string Logger::logFile_;
|
std::string Logger::logFile_;
|
||||||
|
void *Logger::logFileHandle_ = nullptr;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取日志级别字符串
|
|
||||||
* @param level 日志级别
|
|
||||||
* @return 级别对应的字符串表示
|
|
||||||
*/
|
|
||||||
const char *Logger::getLevelString(LogLevel level) {
|
const char *Logger::getLevelString(LogLevel level) {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case LogLevel::Trace:
|
case LogLevel::Trace:
|
||||||
return "TRACE";
|
return "TRACE";
|
||||||
case LogLevel::Debug:
|
case LogLevel::Debug:
|
||||||
return "DEBUG";
|
return "DEBUG";
|
||||||
case LogLevel::Info:
|
case LogLevel::Info:
|
||||||
return "INFO ";
|
return "INFO ";
|
||||||
case LogLevel::Warn:
|
case LogLevel::Warn:
|
||||||
return "WARN ";
|
return "WARN ";
|
||||||
case LogLevel::Error:
|
case LogLevel::Error:
|
||||||
return "ERROR";
|
return "ERROR";
|
||||||
case LogLevel::Fatal:
|
case LogLevel::Fatal:
|
||||||
return "FATAL";
|
return "FATAL";
|
||||||
default:
|
default:
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::writeToConsole(LogLevel level, const char *msg) {
|
||||||
|
const char *levelStr = getLevelString(level);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
|
WORD color = 7;
|
||||||
|
switch (level) {
|
||||||
|
case LogLevel::Trace: color = 8; break;
|
||||||
|
case LogLevel::Debug: color = 8; break;
|
||||||
|
case LogLevel::Info: color = 7; break;
|
||||||
|
case LogLevel::Warn: color = 14; break;
|
||||||
|
case LogLevel::Error: color = 12; break;
|
||||||
|
case LogLevel::Fatal: color = 12 | FOREGROUND_INTENSITY; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
SetConsoleTextAttribute(hConsole, color);
|
||||||
|
printf("[%s] %s\n", levelStr, msg);
|
||||||
|
SetConsoleTextAttribute(hConsole, 7);
|
||||||
|
#else
|
||||||
|
const char *colorCode = "\033[0m";
|
||||||
|
switch (level) {
|
||||||
|
case LogLevel::Trace: colorCode = "\033[90m"; break;
|
||||||
|
case LogLevel::Debug: colorCode = "\033[90m"; break;
|
||||||
|
case LogLevel::Info: colorCode = "\033[0m"; break;
|
||||||
|
case LogLevel::Warn: colorCode = "\033[33m"; break;
|
||||||
|
case LogLevel::Error: colorCode = "\033[31m"; break;
|
||||||
|
case LogLevel::Fatal: colorCode = "\033[1;31m"; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
printf("%s[%s] %s\033[0m\n", colorCode, levelStr, msg);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::writeToFile(LogLevel level, const char *msg) {
|
||||||
|
if (!logFileHandle_) return;
|
||||||
|
|
||||||
|
FILE *fp = static_cast<FILE *>(logFileHandle_);
|
||||||
|
|
||||||
|
time_t now = time(nullptr);
|
||||||
|
struct tm *tm_info = localtime(&now);
|
||||||
|
char timeBuf[32];
|
||||||
|
strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", tm_info);
|
||||||
|
|
||||||
|
fprintf(fp, "[%s] [%s] %s\n", timeBuf, getLevelString(level), msg);
|
||||||
|
fflush(fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::outputLog(LogLevel level, const char *msg) {
|
||||||
|
if (consoleOutput_) {
|
||||||
|
writeToConsole(level, msg);
|
||||||
|
}
|
||||||
|
if (fileOutput_) {
|
||||||
|
writeToFile(level, msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化日志系统
|
|
||||||
*
|
|
||||||
* 初始化日志系统并设置SDL日志级别为详细模式,允许所有级别的日志输出
|
|
||||||
*/
|
|
||||||
void Logger::init() {
|
void Logger::init() {
|
||||||
if (initialized_) {
|
if (initialized_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置 SDL 日志级别为详细模式(允许所有级别的日志)
|
#ifdef _WIN32
|
||||||
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_VERBOSE);
|
SetConsoleOutputCP(CP_UTF8);
|
||||||
|
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
|
if (hOut != INVALID_HANDLE_VALUE) {
|
||||||
|
DWORD mode = 0;
|
||||||
|
if (GetConsoleMode(hOut, &mode)) {
|
||||||
|
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||||
|
SetConsoleMode(hOut, mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
initialized_ = true;
|
#ifdef __SWITCH__
|
||||||
log(LogLevel::Info, "Logger initialized with SDL2");
|
consoleInit(NULL);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
initialized_ = true;
|
||||||
|
log(LogLevel::Info, "Logger initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭日志系统
|
|
||||||
*
|
|
||||||
* 关闭日志系统并输出关闭日志
|
|
||||||
*/
|
|
||||||
void Logger::shutdown() {
|
void Logger::shutdown() {
|
||||||
if (initialized_) {
|
if (initialized_) {
|
||||||
log(LogLevel::Info, "Logger shutting down");
|
log(LogLevel::Info, "Logger shutting down");
|
||||||
}
|
}
|
||||||
initialized_ = false;
|
|
||||||
|
if (logFileHandle_) {
|
||||||
|
fclose(static_cast<FILE *>(logFileHandle_));
|
||||||
|
logFileHandle_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
consoleExit(NULL);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
initialized_ = false;
|
||||||
|
fileOutput_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置日志级别
|
|
||||||
* @param level 要设置的日志级别
|
|
||||||
*
|
|
||||||
* 设置日志系统的最低输出级别,低于此级别的日志将被忽略
|
|
||||||
*/
|
|
||||||
void Logger::setLevel(LogLevel level) {
|
void Logger::setLevel(LogLevel level) {
|
||||||
level_ = level;
|
level_ = level;
|
||||||
// 同时设置 SDL 的日志级别
|
|
||||||
if (level != LogLevel::Off) {
|
|
||||||
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
static_cast<SDL_LogPriority>(level));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置是否输出到控制台
|
|
||||||
* @param enable true启用控制台输出,false禁用
|
|
||||||
*
|
|
||||||
* 控制日志是否输出到控制台,通过调整SDL日志优先级实现
|
|
||||||
*/
|
|
||||||
void Logger::setConsoleOutput(bool enable) {
|
void Logger::setConsoleOutput(bool enable) {
|
||||||
consoleOutput_ = enable;
|
consoleOutput_ = enable;
|
||||||
// SDL2 日志默认输出到控制台,通过设置日志优先级控制
|
|
||||||
if (!enable) {
|
|
||||||
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_CRITICAL);
|
|
||||||
} else {
|
|
||||||
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
static_cast<SDL_LogPriority>(level_));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置日志输出到文件
|
|
||||||
* @param filename 日志文件路径
|
|
||||||
*
|
|
||||||
* 配置日志输出到指定文件,空字符串则禁用文件输出
|
|
||||||
*/
|
|
||||||
void Logger::setFileOutput(const std::string &filename) {
|
void Logger::setFileOutput(const std::string &filename) {
|
||||||
logFile_ = filename;
|
if (logFileHandle_) {
|
||||||
fileOutput_ = !filename.empty();
|
fclose(static_cast<FILE *>(logFileHandle_));
|
||||||
|
logFileHandle_ = nullptr;
|
||||||
if (fileOutput_) {
|
}
|
||||||
// SDL2 使用 SDL_LogSetOutputFunction 可以重定向日志输出
|
|
||||||
// 这里我们记录文件路径,实际文件输出可以通过自定义回调实现
|
logFile_ = filename;
|
||||||
log(LogLevel::Info, "File output configured: {}", filename);
|
fileOutput_ = !filename.empty();
|
||||||
}
|
|
||||||
|
if (fileOutput_) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
FILE *fp = nullptr;
|
||||||
|
fopen_s(&fp, filename.c_str(), "a");
|
||||||
|
#else
|
||||||
|
FILE *fp = fopen(filename.c_str(), "a");
|
||||||
|
#endif
|
||||||
|
logFileHandle_ = fp;
|
||||||
|
|
||||||
|
if (fp) {
|
||||||
|
time_t now = time(nullptr);
|
||||||
|
struct tm *tm_info = localtime(&now);
|
||||||
|
char timeBuf[32];
|
||||||
|
strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", tm_info);
|
||||||
|
fprintf(fp, "\n=== Log session started at %s ===\n", timeBuf);
|
||||||
|
fflush(fp);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
#include <extra2d/utils/logger_module.h>
|
||||||
|
#include <extra2d/config/module_registry.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
bool LoggerModuleConfig::loadFromJson(const void* jsonData) {
|
||||||
|
if (!jsonData) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const json& j = *static_cast<const json*>(jsonData);
|
||||||
|
|
||||||
|
if (j.contains("logLevel")) {
|
||||||
|
int level = j["logLevel"].get<int>();
|
||||||
|
if (level >= 0 && level <= 6) {
|
||||||
|
logLevel = static_cast<LogLevel>(level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j.contains("consoleOutput")) {
|
||||||
|
consoleOutput = j["consoleOutput"].get<bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j.contains("fileOutput")) {
|
||||||
|
fileOutput = j["fileOutput"].get<bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j.contains("logFilePath")) {
|
||||||
|
logFilePath = j["logFilePath"].get<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoggerModuleConfig::saveToJson(void* jsonData) const {
|
||||||
|
if (!jsonData) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
json& j = *static_cast<json*>(jsonData);
|
||||||
|
j["logLevel"] = static_cast<int>(logLevel);
|
||||||
|
j["consoleOutput"] = consoleOutput;
|
||||||
|
j["fileOutput"] = fileOutput;
|
||||||
|
j["logFilePath"] = logFilePath;
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LoggerModuleInitializer::LoggerModuleInitializer()
|
||||||
|
: moduleId_(INVALID_MODULE_ID)
|
||||||
|
, initialized_(false) {
|
||||||
|
}
|
||||||
|
|
||||||
|
LoggerModuleInitializer::~LoggerModuleInitializer() {
|
||||||
|
if (initialized_) {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoggerModuleInitializer::initialize(const IModuleConfig* config) {
|
||||||
|
if (initialized_) return true;
|
||||||
|
|
||||||
|
const LoggerModuleConfig* loggerConfig = dynamic_cast<const LoggerModuleConfig*>(config);
|
||||||
|
|
||||||
|
Logger::init();
|
||||||
|
|
||||||
|
if (loggerConfig) {
|
||||||
|
Logger::setLevel(loggerConfig->logLevel);
|
||||||
|
Logger::setConsoleOutput(loggerConfig->consoleOutput);
|
||||||
|
if (loggerConfig->fileOutput && !loggerConfig->logFilePath.empty()) {
|
||||||
|
Logger::setFileOutput(loggerConfig->logFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initialized_ = true;
|
||||||
|
E2D_LOG_INFO("Logger module initialized");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoggerModuleInitializer::shutdown() {
|
||||||
|
if (!initialized_) return;
|
||||||
|
|
||||||
|
E2D_LOG_INFO("Logger module shutting down");
|
||||||
|
Logger::shutdown();
|
||||||
|
initialized_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
static ModuleId s_loggerModuleId = INVALID_MODULE_ID;
|
||||||
|
|
||||||
|
struct LoggerModuleRegistrar {
|
||||||
|
LoggerModuleRegistrar() {
|
||||||
|
s_loggerModuleId = ModuleRegistry::instance().registerModule(
|
||||||
|
makeUnique<LoggerModuleConfig>(),
|
||||||
|
[]() -> UniquePtr<IModuleInitializer> {
|
||||||
|
auto initializer = makeUnique<LoggerModuleInitializer>();
|
||||||
|
initializer->setModuleId(s_loggerModuleId);
|
||||||
|
return initializer;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static LoggerModuleRegistrar s_registrar;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
Loading…
Reference in New Issue