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/config_manager.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 <string>
|
||||
|
||||
|
|
@ -18,198 +16,58 @@ class EventQueue;
|
|||
class EventDispatcher;
|
||||
class Camera;
|
||||
class ViewportAdapter;
|
||||
class RenderBackend;
|
||||
|
||||
/**
|
||||
* @brief Application 单例 - 应用主控
|
||||
*
|
||||
* 负责管理应用程序的整个生命周期,包括初始化、主循环、渲染和关闭。
|
||||
* 集成了 ConfigManager 和 ModuleRegistry 系统进行配置和模块管理。
|
||||
*/
|
||||
class Application {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return Application 实例引用
|
||||
*/
|
||||
static Application& get();
|
||||
|
||||
Application(const Application&) = delete;
|
||||
Application& operator=(const Application&) = delete;
|
||||
|
||||
/**
|
||||
* @brief 使用默认配置初始化
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool init();
|
||||
|
||||
/**
|
||||
* @brief 使用配置结构初始化
|
||||
* @param config 应用配置
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool init(const AppConfig& config);
|
||||
|
||||
/**
|
||||
* @brief 从配置文件初始化
|
||||
* @param configPath 配置文件路径(支持 .json 和 .ini)
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool init(const std::string& configPath);
|
||||
|
||||
/**
|
||||
* @brief 关闭应用程序
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 运行主循环
|
||||
*/
|
||||
void run();
|
||||
|
||||
/**
|
||||
* @brief 退出应用程序
|
||||
*/
|
||||
void quit();
|
||||
|
||||
/**
|
||||
* @brief 暂停应用程序
|
||||
*/
|
||||
void pause();
|
||||
|
||||
/**
|
||||
* @brief 恢复应用程序
|
||||
*/
|
||||
void resume();
|
||||
|
||||
/**
|
||||
* @brief 检查是否暂停
|
||||
* @return 暂停状态返回 true
|
||||
*/
|
||||
bool isPaused() const { return paused_; }
|
||||
|
||||
/**
|
||||
* @brief 检查是否运行中
|
||||
* @return 运行中返回 true
|
||||
*/
|
||||
bool isRunning() const { return running_; }
|
||||
|
||||
/**
|
||||
* @brief 获取窗口接口
|
||||
* @return 窗口接口引用
|
||||
*/
|
||||
IWindow& window() { return *window_; }
|
||||
|
||||
/**
|
||||
* @brief 获取渲染后端
|
||||
* @return 渲染后端引用
|
||||
*/
|
||||
RenderBackend& renderer() { return *renderer_; }
|
||||
|
||||
/**
|
||||
* @brief 获取输入接口
|
||||
* @return 输入接口引用
|
||||
*/
|
||||
RenderBackend& renderer();
|
||||
IInput& input();
|
||||
|
||||
/**
|
||||
* @brief 获取场景管理器
|
||||
* @return 场景管理器引用
|
||||
*/
|
||||
SceneManager& scenes();
|
||||
|
||||
/**
|
||||
* @brief 获取定时器管理器
|
||||
* @return 定时器管理器引用
|
||||
*/
|
||||
TimerManager& timers();
|
||||
|
||||
/**
|
||||
* @brief 获取事件队列
|
||||
* @return 事件队列引用
|
||||
*/
|
||||
EventQueue& eventQueue();
|
||||
|
||||
/**
|
||||
* @brief 获取事件分发器
|
||||
* @return 事件分发器引用
|
||||
*/
|
||||
EventDispatcher& eventDispatcher();
|
||||
|
||||
/**
|
||||
* @brief 获取相机
|
||||
* @return 相机引用
|
||||
*/
|
||||
Camera& camera();
|
||||
|
||||
/**
|
||||
* @brief 获取视口适配器
|
||||
* @return 视口适配器引用
|
||||
*/
|
||||
ViewportAdapter& viewportAdapter();
|
||||
|
||||
/**
|
||||
* @brief 进入场景
|
||||
* @param scene 场景指针
|
||||
*/
|
||||
void enterScene(Ptr<class Scene> scene);
|
||||
|
||||
/**
|
||||
* @brief 获取帧时间
|
||||
* @return 帧时间(秒)
|
||||
*/
|
||||
float deltaTime() const { return deltaTime_; }
|
||||
|
||||
/**
|
||||
* @brief 获取总运行时间
|
||||
* @return 总运行时间(秒)
|
||||
*/
|
||||
float totalTime() const { return totalTime_; }
|
||||
|
||||
/**
|
||||
* @brief 获取当前帧率
|
||||
* @return 帧率(FPS)
|
||||
*/
|
||||
int fps() const { return currentFps_; }
|
||||
|
||||
/**
|
||||
* @brief 获取配置管理器
|
||||
* @return 配置管理器引用
|
||||
*/
|
||||
ConfigManager& config();
|
||||
|
||||
/**
|
||||
* @brief 获取应用配置
|
||||
* @return 应用配置常量引用
|
||||
*/
|
||||
const AppConfig& getConfig() const;
|
||||
|
||||
private:
|
||||
Application() = default;
|
||||
~Application();
|
||||
|
||||
/**
|
||||
* @brief 内部初始化实现
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool initImpl();
|
||||
|
||||
/**
|
||||
* @brief 主循环
|
||||
*/
|
||||
bool initModules();
|
||||
void mainLoop();
|
||||
|
||||
/**
|
||||
* @brief 更新逻辑
|
||||
*/
|
||||
void update();
|
||||
|
||||
/**
|
||||
* @brief 渲染
|
||||
*/
|
||||
void render();
|
||||
|
||||
UniquePtr<IWindow> window_;
|
||||
UniquePtr<RenderBackend> renderer_;
|
||||
IWindow* window_ = nullptr;
|
||||
UniquePtr<SceneManager> sceneManager_;
|
||||
UniquePtr<TimerManager> timerManager_;
|
||||
UniquePtr<EventQueue> eventQueue_;
|
||||
|
|
@ -228,10 +86,6 @@ private:
|
|||
int frameCount_ = 0;
|
||||
float fpsTimer_ = 0.0f;
|
||||
int currentFps_ = 0;
|
||||
|
||||
ModuleId windowModuleId_ = INVALID_MODULE_ID;
|
||||
ModuleId inputModuleId_ = INVALID_MODULE_ID;
|
||||
ModuleId renderModuleId_ = INVALID_MODULE_ID;
|
||||
};
|
||||
|
||||
} // 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 模块注册表条目结构体
|
||||
* 存储模块的配置和初始化器工厂
|
||||
* 存储模块的配置和初始化器
|
||||
*/
|
||||
struct ModuleEntry {
|
||||
ModuleId id; ///< 模块标识符
|
||||
UniquePtr<IModuleConfig> config; ///< 模块配置
|
||||
ModuleInitializerFactory initializerFactory;///< 初始化器工厂函数
|
||||
UniquePtr<IModuleInitializer> initializer; ///< 初始化器实例
|
||||
bool initialized = false; ///< 是否已初始化
|
||||
};
|
||||
|
||||
|
|
@ -74,11 +75,11 @@ public:
|
|||
IModuleConfig* getModuleConfigByName(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief 创建模块初始化器
|
||||
* @brief 获取或创建模块初始化器
|
||||
* @param id 模块标识符
|
||||
* @return 初始化器实例,不存在返回 nullptr
|
||||
* @return 初始化器指针,不存在返回 nullptr
|
||||
*/
|
||||
UniquePtr<IModuleInitializer> createInitializer(ModuleId id) const;
|
||||
IModuleInitializer* getInitializer(ModuleId id);
|
||||
|
||||
/**
|
||||
* @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 {
|
||||
|
||||
/**
|
||||
* @brief 渲染模块配置
|
||||
* 实现 IModuleConfig 接口
|
||||
*/
|
||||
class RenderModuleConfig : public IModuleConfig {
|
||||
public:
|
||||
BackendType backend = BackendType::OpenGL;
|
||||
|
|
@ -20,10 +16,6 @@ public:
|
|||
bool sRGBFramebuffer = false;
|
||||
int spriteBatchSize = 1000;
|
||||
|
||||
/**
|
||||
* @brief 获取模块信息
|
||||
* @return 模块信息结构体
|
||||
*/
|
||||
ModuleInfo getModuleInfo() const override {
|
||||
ModuleInfo info;
|
||||
info.name = "Render";
|
||||
|
|
@ -33,115 +25,41 @@ public:
|
|||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取配置节名称
|
||||
* @return 配置节名称字符串
|
||||
*/
|
||||
std::string getConfigSectionName() const override { return "render"; }
|
||||
|
||||
/**
|
||||
* @brief 验证配置有效性
|
||||
* @return 如果配置有效返回 true
|
||||
*/
|
||||
bool validate() const override;
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* @brief 保存配置到 JSON 数据
|
||||
* @param jsonData JSON 数据指针
|
||||
* @return 保存成功返回 true
|
||||
*/
|
||||
bool saveToJson(void* jsonData) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 渲染模块初始化器
|
||||
* 实现 IModuleInitializer 接口
|
||||
* 依赖窗口模块
|
||||
*/
|
||||
class RenderModuleInitializer : public IModuleInitializer {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*/
|
||||
RenderModuleInitializer();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~RenderModuleInitializer() override;
|
||||
|
||||
/**
|
||||
* @brief 获取模块标识符
|
||||
* @return 模块唯一标识符
|
||||
*/
|
||||
ModuleId getModuleId() const override { return moduleId_; }
|
||||
|
||||
/**
|
||||
* @brief 获取模块优先级
|
||||
* @return 模块优先级
|
||||
*/
|
||||
ModulePriority getPriority() const override { return ModulePriority::Graphics; }
|
||||
|
||||
/**
|
||||
* @brief 获取模块依赖列表
|
||||
* @return 依赖模块标识符列表
|
||||
*/
|
||||
std::vector<ModuleId> getDependencies() const override;
|
||||
|
||||
/**
|
||||
* @brief 初始化模块
|
||||
* @param config 模块配置指针
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool initialize(const IModuleConfig* config) override;
|
||||
|
||||
/**
|
||||
* @brief 关闭模块
|
||||
*/
|
||||
void shutdown() override;
|
||||
|
||||
/**
|
||||
* @brief 检查模块是否已初始化
|
||||
* @return 已初始化返回 true
|
||||
*/
|
||||
bool isInitialized() const override { return initialized_; }
|
||||
|
||||
/**
|
||||
* @brief 获取渲染器实例
|
||||
* @return 渲染后端指针
|
||||
*/
|
||||
void setModuleId(ModuleId id) { moduleId_ = id; }
|
||||
void setWindow(IWindow* window) { window_ = window; }
|
||||
|
||||
RenderBackend* getRenderer() const { return renderer_.get(); }
|
||||
|
||||
/**
|
||||
* @brief 设置窗口模块标识符
|
||||
* @param windowModuleId 窗口模块标识符
|
||||
*/
|
||||
void setWindowModuleId(ModuleId windowModuleId) { windowModuleId_ = windowModuleId; }
|
||||
|
||||
|
||||
private:
|
||||
ModuleId moduleId_ = INVALID_MODULE_ID;
|
||||
ModuleId windowModuleId_ = INVALID_MODULE_ID;
|
||||
IWindow* window_ = nullptr;
|
||||
UniquePtr<RenderBackend> renderer_;
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
ModuleId get_render_module_id();
|
||||
void register_render_module();
|
||||
|
||||
} // 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/app_config.h>
|
||||
#include <extra2d/platform/iwindow.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 窗口模块配置
|
||||
* 实现 IModuleConfig 接口
|
||||
*/
|
||||
class WindowModuleConfig : public IModuleConfig {
|
||||
public:
|
||||
WindowConfigData windowConfig;
|
||||
std::string backend = "sdl2";
|
||||
|
||||
/**
|
||||
* @brief 获取模块信息
|
||||
* @return 模块信息结构体
|
||||
*/
|
||||
WindowConfigData windowConfig;
|
||||
|
||||
ModuleInfo getModuleInfo() const override {
|
||||
ModuleInfo info;
|
||||
info.id = 0;
|
||||
info.name = "Window";
|
||||
info.version = "1.0.0";
|
||||
info.priority = ModulePriority::Platform;
|
||||
info.priority = ModulePriority::Core;
|
||||
info.enabled = true;
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取配置节名称
|
||||
* @return 配置节名称字符串
|
||||
*/
|
||||
std::string getConfigSectionName() const override { return "window"; }
|
||||
|
||||
/**
|
||||
* @brief 验证配置有效性
|
||||
* @return 如果配置有效返回 true
|
||||
*/
|
||||
bool validate() const override;
|
||||
|
||||
/**
|
||||
* @brief 应用平台约束
|
||||
* 根据平台特性调整配置
|
||||
* @param platform 目标平台类型
|
||||
*/
|
||||
void applyPlatformConstraints(PlatformType platform) override;
|
||||
|
||||
/**
|
||||
* @brief 重置为默认配置
|
||||
*/
|
||||
void resetToDefaults() override;
|
||||
|
||||
/**
|
||||
* @brief 从 JSON 数据加载配置
|
||||
* @param jsonData JSON 数据指针
|
||||
* @return 加载成功返回 true
|
||||
*/
|
||||
|
||||
std::string getConfigSectionName() const override {
|
||||
return "window";
|
||||
}
|
||||
|
||||
bool validate() const override {
|
||||
return windowConfig.width > 0 && windowConfig.height > 0;
|
||||
}
|
||||
|
||||
void resetToDefaults() override {
|
||||
backend = "sdl2";
|
||||
windowConfig = WindowConfigData{};
|
||||
}
|
||||
|
||||
bool loadFromJson(const void* jsonData) override;
|
||||
|
||||
/**
|
||||
* @brief 保存配置到 JSON 数据
|
||||
* @param jsonData JSON 数据指针
|
||||
* @return 保存成功返回 true
|
||||
*/
|
||||
bool saveToJson(void* jsonData) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 窗口模块初始化器
|
||||
* 实现 IModuleInitializer 接口
|
||||
*/
|
||||
class WindowModuleInitializer : public IModuleInitializer {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*/
|
||||
WindowModuleInitializer();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~WindowModuleInitializer() override;
|
||||
|
||||
/**
|
||||
* @brief 获取模块标识符
|
||||
* @return 模块唯一标识符
|
||||
*/
|
||||
|
||||
ModuleId getModuleId() const override { return moduleId_; }
|
||||
|
||||
/**
|
||||
* @brief 获取模块优先级
|
||||
* @return 模块优先级
|
||||
*/
|
||||
ModulePriority getPriority() const override { return ModulePriority::Platform; }
|
||||
|
||||
/**
|
||||
* @brief 获取模块依赖列表
|
||||
* @return 依赖模块标识符列表
|
||||
*/
|
||||
ModulePriority getPriority() const override { return ModulePriority::Core; }
|
||||
std::vector<ModuleId> getDependencies() const override { return {}; }
|
||||
|
||||
/**
|
||||
* @brief 初始化模块
|
||||
* @param config 模块配置指针
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
|
||||
bool initialize(const IModuleConfig* config) override;
|
||||
|
||||
/**
|
||||
* @brief 关闭模块
|
||||
*/
|
||||
void shutdown() override;
|
||||
|
||||
/**
|
||||
* @brief 检查模块是否已初始化
|
||||
* @return 已初始化返回 true
|
||||
*/
|
||||
bool isInitialized() const override { return initialized_; }
|
||||
|
||||
/**
|
||||
* @brief 获取窗口实例
|
||||
* @return 窗口接口指针
|
||||
*/
|
||||
|
||||
void setModuleId(ModuleId id) { moduleId_ = id; }
|
||||
void setWindowConfig(const WindowConfigData& config) { windowConfig_ = config; }
|
||||
|
||||
IWindow* getWindow() const { return window_.get(); }
|
||||
|
||||
|
||||
private:
|
||||
bool initBackend();
|
||||
bool createWindow(const std::string& backend, const WindowConfigData& config);
|
||||
void shutdownBackend();
|
||||
|
||||
ModuleId moduleId_ = INVALID_MODULE_ID;
|
||||
UniquePtr<IWindow> window_;
|
||||
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
|
||||
|
|
|
|||
|
|
@ -6,254 +6,186 @@
|
|||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
// SDL2 日志头文件
|
||||
#include <SDL.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 日志级别枚举 - 映射到 SDL_LogPriority
|
||||
// ============================================================================
|
||||
enum class LogLevel {
|
||||
Trace = SDL_LOG_PRIORITY_VERBOSE, // SDL 详细日志
|
||||
Debug = SDL_LOG_PRIORITY_DEBUG, // SDL 调试日志
|
||||
Info = SDL_LOG_PRIORITY_INFO, // SDL 信息日志
|
||||
Warn = SDL_LOG_PRIORITY_WARN, // SDL 警告日志
|
||||
Error = SDL_LOG_PRIORITY_ERROR, // SDL 错误日志
|
||||
Fatal = SDL_LOG_PRIORITY_CRITICAL, // SDL 严重日志
|
||||
Off = SDL_LOG_PRIORITY_CRITICAL + 1 // 关闭日志 (使用 Critical+1 作为关闭标记)
|
||||
Trace = 0,
|
||||
Debug = 1,
|
||||
Info = 2,
|
||||
Warn = 3,
|
||||
Error = 4,
|
||||
Fatal = 5,
|
||||
Off = 6
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 简单的 fmt-style {} 格式化器
|
||||
// ============================================================================
|
||||
namespace detail {
|
||||
|
||||
// 将单个参数转为字符串
|
||||
template <typename T> inline std::string to_string_arg(const T &value) {
|
||||
if constexpr (std::is_same_v<T, std::string>) {
|
||||
return value;
|
||||
} else if constexpr (std::is_same_v<T, const char *> ||
|
||||
std::is_same_v<T, char *>) {
|
||||
return value ? std::string(value) : std::string("(null)");
|
||||
} else if constexpr (std::is_same_v<T, bool>) {
|
||||
return value ? "true" : "false";
|
||||
} else if constexpr (std::is_arithmetic_v<T>) {
|
||||
// 对浮点数使用特殊格式
|
||||
if constexpr (std::is_floating_point_v<T>) {
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "%.2f", static_cast<double>(value));
|
||||
return buf;
|
||||
if constexpr (std::is_same_v<T, std::string>) {
|
||||
return value;
|
||||
} else if constexpr (std::is_same_v<T, const char *> ||
|
||||
std::is_same_v<T, char *>) {
|
||||
return value ? std::string(value) : std::string("(null)");
|
||||
} else if constexpr (std::is_same_v<T, bool>) {
|
||||
return value ? "true" : "false";
|
||||
} else if constexpr (std::is_arithmetic_v<T>) {
|
||||
if constexpr (std::is_floating_point_v<T>) {
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "%.2f", static_cast<double>(value));
|
||||
return buf;
|
||||
} else {
|
||||
return std::to_string(value);
|
||||
}
|
||||
} 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) {
|
||||
std::string result;
|
||||
while (*fmt) {
|
||||
if (*fmt == '{' && *(fmt + 1) == '}') {
|
||||
result += "{}"; // 无参数可替换,保留原样
|
||||
fmt += 2;
|
||||
} else {
|
||||
result += *fmt;
|
||||
++fmt;
|
||||
std::string result;
|
||||
while (*fmt) {
|
||||
if (*fmt == '{' && *(fmt + 1) == '}') {
|
||||
result += "{}";
|
||||
fmt += 2;
|
||||
} else {
|
||||
result += *fmt;
|
||||
++fmt;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
// 格式化递归:替换第一个 {} 并递归处理剩余
|
||||
template <typename T, typename... Args>
|
||||
inline std::string format_impl(const char *fmt, const T &first,
|
||||
const Args &...rest) {
|
||||
std::string result;
|
||||
while (*fmt) {
|
||||
if (*fmt == '{') {
|
||||
// 检查 {:#x} 等格式说明符
|
||||
if (*(fmt + 1) == '}') {
|
||||
result += to_string_arg(first);
|
||||
fmt += 2;
|
||||
result += format_impl(fmt, rest...);
|
||||
return result;
|
||||
} else if (*(fmt + 1) == ':') {
|
||||
// 跳过格式说明符直到 }
|
||||
const char *end = fmt + 2;
|
||||
while (*end && *end != '}')
|
||||
++end;
|
||||
if (*end == '}') {
|
||||
// 检查是否是十六进制格式
|
||||
std::string spec(fmt + 2, end);
|
||||
if (spec.find('x') != std::string::npos ||
|
||||
spec.find('X') != std::string::npos) {
|
||||
if constexpr (std::is_integral_v<T>) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "0x%x",
|
||||
static_cast<unsigned int>(first));
|
||||
result += buf;
|
||||
} else {
|
||||
result += to_string_arg(first);
|
||||
}
|
||||
} else if (spec.find('f') != std::string::npos ||
|
||||
spec.find('.') != std::string::npos) {
|
||||
if constexpr (std::is_arithmetic_v<T>) {
|
||||
// 解析精度
|
||||
int precision = 2;
|
||||
auto dot = spec.find('.');
|
||||
if (dot != std::string::npos) {
|
||||
precision = 0;
|
||||
for (size_t i = dot + 1;
|
||||
i < spec.size() && spec[i] >= '0' && spec[i] <= '9'; ++i) {
|
||||
precision = precision * 10 + (spec[i] - '0');
|
||||
std::string result;
|
||||
while (*fmt) {
|
||||
if (*fmt == '{') {
|
||||
if (*(fmt + 1) == '}') {
|
||||
result += to_string_arg(first);
|
||||
fmt += 2;
|
||||
result += format_impl(fmt, rest...);
|
||||
return result;
|
||||
} else if (*(fmt + 1) == ':') {
|
||||
const char *end = fmt + 2;
|
||||
while (*end && *end != '}')
|
||||
++end;
|
||||
if (*end == '}') {
|
||||
std::string spec(fmt + 2, end);
|
||||
if (spec.find('x') != std::string::npos ||
|
||||
spec.find('X') != std::string::npos) {
|
||||
if constexpr (std::is_integral_v<T>) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "0x%x",
|
||||
static_cast<unsigned int>(first));
|
||||
result += buf;
|
||||
} else {
|
||||
result += to_string_arg(first);
|
||||
}
|
||||
} else if (spec.find('f') != std::string::npos ||
|
||||
spec.find('.') != std::string::npos) {
|
||||
if constexpr (std::is_arithmetic_v<T>) {
|
||||
int precision = 2;
|
||||
auto dot = spec.find('.');
|
||||
if (dot != std::string::npos) {
|
||||
precision = 0;
|
||||
for (size_t i = dot + 1;
|
||||
i < spec.size() && spec[i] >= '0' && spec[i] <= '9'; ++i) {
|
||||
precision = precision * 10 + (spec[i] - '0');
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
++fmt;
|
||||
}
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// 顶层格式化函数
|
||||
template <typename... 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); }
|
||||
|
||||
// ============================================================================
|
||||
// Logger 类 - 使用 SDL2 日志系统
|
||||
// ============================================================================
|
||||
class Logger {
|
||||
public:
|
||||
/**
|
||||
* @brief 初始化日志系统
|
||||
*/
|
||||
static void init();
|
||||
static void init();
|
||||
static void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 关闭日志系统
|
||||
*/
|
||||
static void shutdown();
|
||||
static void setLevel(LogLevel level);
|
||||
static void setConsoleOutput(bool enable);
|
||||
static void setFileOutput(const std::string &filename);
|
||||
|
||||
/**
|
||||
* @brief 设置日志级别
|
||||
* @param level 日志级别
|
||||
*/
|
||||
static void setLevel(LogLevel level);
|
||||
static LogLevel getLevel() { return level_; }
|
||||
|
||||
/**
|
||||
* @brief 设置是否输出到控制台
|
||||
* @param enable 是否启用
|
||||
*/
|
||||
static void setConsoleOutput(bool enable);
|
||||
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...);
|
||||
outputLog(level, msg.c_str());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置日志输出到文件
|
||||
* @param filename 日志文件名
|
||||
*/
|
||||
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);
|
||||
}
|
||||
static void log(LogLevel level, const char *msg) {
|
||||
if (static_cast<int>(level) < static_cast<int>(level_))
|
||||
return;
|
||||
outputLog(level, msg);
|
||||
}
|
||||
|
||||
private:
|
||||
static LogLevel level_; // 当前日志级别
|
||||
static bool initialized_; // 是否已初始化
|
||||
static bool consoleOutput_; // 是否输出到控制台
|
||||
static bool fileOutput_; // 是否输出到文件
|
||||
static std::string logFile_; // 日志文件路径
|
||||
static void outputLog(LogLevel level, const char *msg);
|
||||
static const char *getLevelString(LogLevel level);
|
||||
static void writeToConsole(LogLevel level, const char *msg);
|
||||
static void writeToFile(LogLevel level, const char *msg);
|
||||
|
||||
/**
|
||||
* @brief 获取日志级别字符串
|
||||
* @param level 日志级别
|
||||
* @return 级别字符串
|
||||
*/
|
||||
static const char *getLevelString(LogLevel level);
|
||||
static LogLevel level_;
|
||||
static bool initialized_;
|
||||
static bool consoleOutput_;
|
||||
static bool fileOutput_;
|
||||
static std::string logFile_;
|
||||
static void *logFileHandle_;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 日志宏
|
||||
// ============================================================================
|
||||
|
||||
#ifdef E2D_DEBUG
|
||||
#define E2D_LOG_TRACE(...) \
|
||||
::extra2d::Logger::log(::extra2d::LogLevel::Trace, __VA_ARGS__)
|
||||
::extra2d::Logger::log(::extra2d::LogLevel::Trace, __VA_ARGS__)
|
||||
#define E2D_LOG_DEBUG(...) \
|
||||
::extra2d::Logger::log(::extra2d::LogLevel::Debug, __VA_ARGS__)
|
||||
::extra2d::Logger::log(::extra2d::LogLevel::Debug, __VA_ARGS__)
|
||||
#else
|
||||
#define E2D_LOG_TRACE(...)
|
||||
#define E2D_LOG_DEBUG(...)
|
||||
#endif
|
||||
|
||||
#define E2D_LOG_INFO(...) \
|
||||
::extra2d::Logger::log(::extra2d::LogLevel::Info, __VA_ARGS__)
|
||||
::extra2d::Logger::log(::extra2d::LogLevel::Info, __VA_ARGS__)
|
||||
#define E2D_LOG_WARN(...) \
|
||||
::extra2d::Logger::log(::extra2d::LogLevel::Warn, __VA_ARGS__)
|
||||
::extra2d::Logger::log(::extra2d::LogLevel::Warn, __VA_ARGS__)
|
||||
#define E2D_LOG_ERROR(...) \
|
||||
::extra2d::Logger::log(::extra2d::LogLevel::Error, __VA_ARGS__)
|
||||
::extra2d::Logger::log(::extra2d::LogLevel::Error, __VA_ARGS__)
|
||||
#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_WARN(...) E2D_LOG_WARN(__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/config/config_manager.h>
|
||||
#include <extra2d/config/config_module.h>
|
||||
#include <extra2d/config/module_registry.h>
|
||||
#include <extra2d/event/event_dispatcher.h>
|
||||
#include <extra2d/event/event_queue.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/vram_manager.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/utils/logger.h>
|
||||
#include <extra2d/utils/timer.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#ifdef __SWITCH__
|
||||
#include <switch.h>
|
||||
#endif
|
||||
|
||||
#ifdef E2D_BACKEND_SDL2
|
||||
namespace extra2d {
|
||||
void initSDL2Backend();
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 获取当前时间(秒)
|
||||
* @return 当前时间戳(秒)
|
||||
*/
|
||||
static double getTimeSeconds() {
|
||||
#ifdef __SWITCH__
|
||||
struct timespec ts;
|
||||
|
|
@ -67,170 +51,97 @@ bool Application::init(const AppConfig& config) {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER) != 0) {
|
||||
E2D_LOG_ERROR("Failed to initialize SDL: {}", SDL_GetError());
|
||||
return false;
|
||||
register_config_module();
|
||||
register_platform_module();
|
||||
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();
|
||||
|
||||
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();
|
||||
return initModules();
|
||||
}
|
||||
|
||||
bool Application::init(const std::string& configPath) {
|
||||
if (initialized_) {
|
||||
E2D_LOG_WARN("Application already initialized");
|
||||
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)) {
|
||||
E2D_LOG_WARN("Failed to load config from file, using defaults");
|
||||
if (!ConfigManager::instance().initialize()) {
|
||||
E2D_LOG_ERROR("Failed to initialize ConfigManager");
|
||||
return false;
|
||||
auto* configInit = ModuleRegistry::instance().getInitializer(get_config_module_id());
|
||||
if (configInit) {
|
||||
auto* cfgInit = dynamic_cast<ConfigModuleInitializer*>(configInit);
|
||||
if (cfgInit) {
|
||||
cfgInit->setConfigPath(configPath);
|
||||
}
|
||||
}
|
||||
|
||||
return initImpl();
|
||||
return initModules();
|
||||
}
|
||||
|
||||
bool Application::initImpl() {
|
||||
#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
|
||||
}
|
||||
|
||||
bool Application::initModules() {
|
||||
auto initOrder = ModuleRegistry::instance().getInitializationOrder();
|
||||
E2D_LOG_INFO("Initializing {} registered modules...", initOrder.size());
|
||||
|
||||
|
||||
for (ModuleId moduleId : initOrder) {
|
||||
auto initializer = ModuleRegistry::instance().createInitializer(moduleId);
|
||||
auto* initializer = ModuleRegistry::instance().getInitializer(moduleId);
|
||||
if (!initializer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* moduleConfig = ModuleRegistry::instance().getModuleConfig(moduleId);
|
||||
if (!moduleConfig) {
|
||||
E2D_LOG_WARN("Module {} has no config, skipping", moduleId);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto info = moduleConfig->getModuleInfo();
|
||||
if (!info.enabled) {
|
||||
E2D_LOG_INFO("Module '{}' is disabled, skipping", info.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Initializing module '{}' (priority: {})",
|
||||
info.name, static_cast<int>(info.priority));
|
||||
if (info.name == "Render") {
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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_) {
|
||||
E2D_LOG_ERROR("Failed to create window for backend: {}", backend);
|
||||
return false;
|
||||
}
|
||||
|
||||
WindowConfigData winConfig = appConfig.window;
|
||||
|
||||
if (platform == PlatformType::Switch) {
|
||||
winConfig.mode = WindowMode::Fullscreen;
|
||||
winConfig.resizable = false;
|
||||
}
|
||||
|
||||
if (!window_->create(winConfig)) {
|
||||
E2D_LOG_ERROR("Failed to create window");
|
||||
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;
|
||||
auto* renderInit = ModuleRegistry::instance().getInitializer(get_render_module_id());
|
||||
if (renderInit) {
|
||||
auto* renderModule = dynamic_cast<RenderModuleInitializer*>(renderInit);
|
||||
if (renderModule) {
|
||||
renderModule->setWindow(window_);
|
||||
|
||||
auto* renderConfig = ModuleRegistry::instance().getModuleConfig(get_render_module_id());
|
||||
if (renderConfig && !renderInit->initialize(renderConfig)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sceneManager_ = makeUnique<SceneManager>();
|
||||
|
|
@ -242,13 +153,12 @@ bool Application::initImpl() {
|
|||
|
||||
viewportAdapter_ = makeUnique<ViewportAdapter>();
|
||||
ViewportConfig vpConfig;
|
||||
vpConfig.logicWidth = static_cast<float>(appConfig.window.width);
|
||||
vpConfig.logicHeight = static_cast<float>(appConfig.window.height);
|
||||
vpConfig.logicWidth = static_cast<float>(window_->width());
|
||||
vpConfig.logicHeight = static_cast<float>(window_->height());
|
||||
vpConfig.mode = ViewportMode::AspectRatio;
|
||||
viewportAdapter_->setConfig(vpConfig);
|
||||
|
||||
camera_->setViewportAdapter(viewportAdapter_.get());
|
||||
|
||||
viewportAdapter_->update(window_->width(), window_->height());
|
||||
|
||||
window_->onResize([this](int width, int height) {
|
||||
|
|
@ -272,12 +182,6 @@ bool Application::initImpl() {
|
|||
initialized_ = 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;
|
||||
}
|
||||
|
||||
|
|
@ -285,8 +189,6 @@ void Application::shutdown() {
|
|||
if (!initialized_)
|
||||
return;
|
||||
|
||||
E2D_LOG_INFO("Shutting down application...");
|
||||
|
||||
VRAMMgr::get().printStats();
|
||||
|
||||
if (sceneManager_) {
|
||||
|
|
@ -301,59 +203,24 @@ void Application::shutdown() {
|
|||
eventQueue_.reset();
|
||||
eventDispatcher_.reset();
|
||||
|
||||
if (renderer_) {
|
||||
renderer_->shutdown();
|
||||
renderer_.reset();
|
||||
}
|
||||
window_ = nullptr;
|
||||
|
||||
if (window_) {
|
||||
window_->destroy();
|
||||
window_.reset();
|
||||
}
|
||||
|
||||
auto modules = ModuleRegistry::instance().getAllModules();
|
||||
auto initOrder = ModuleRegistry::instance().getInitializationOrder();
|
||||
|
||||
for (auto it = initOrder.rbegin(); it != initOrder.rend(); ++it) {
|
||||
ModuleId moduleId = *it;
|
||||
auto initializer = ModuleRegistry::instance().createInitializer(moduleId);
|
||||
auto* initializer = ModuleRegistry::instance().getInitializer(moduleId);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
running_ = false;
|
||||
|
||||
E2D_LOG_INFO("Application shutdown complete");
|
||||
}
|
||||
|
||||
void Application::run() {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_ERROR("Application not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -372,7 +239,6 @@ void Application::quit() {
|
|||
void Application::pause() {
|
||||
if (!paused_) {
|
||||
paused_ = true;
|
||||
E2D_LOG_INFO("Application paused");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -380,7 +246,6 @@ void Application::resume() {
|
|||
if (paused_) {
|
||||
paused_ = false;
|
||||
lastFrameTime_ = getTimeSeconds();
|
||||
E2D_LOG_INFO("Application resumed");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -436,24 +301,30 @@ void Application::update() {
|
|||
}
|
||||
|
||||
void Application::render() {
|
||||
if (!renderer_) {
|
||||
E2D_LOG_ERROR("Render failed: renderer is null");
|
||||
auto* renderInit = ModuleRegistry::instance().getInitializer(get_render_module_id());
|
||||
RenderBackend* renderer = nullptr;
|
||||
if (renderInit) {
|
||||
auto* renderModule = dynamic_cast<RenderModuleInitializer*>(renderInit);
|
||||
if (renderModule) {
|
||||
renderer = renderModule->getRenderer();
|
||||
}
|
||||
}
|
||||
|
||||
if (!renderer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (viewportAdapter_) {
|
||||
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.size.width), static_cast<int>(vp.size.height));
|
||||
} else {
|
||||
renderer_->setViewport(0, 0, window_->width(), window_->height());
|
||||
renderer->setViewport(0, 0, window_->width(), window_->height());
|
||||
}
|
||||
|
||||
if (sceneManager_) {
|
||||
sceneManager_->render(*renderer_);
|
||||
} else {
|
||||
E2D_LOG_WARN("Render: sceneManager is null");
|
||||
sceneManager_->render(*renderer);
|
||||
}
|
||||
|
||||
window_->swap();
|
||||
|
|
@ -463,6 +334,21 @@ IInput& Application::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() {
|
||||
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/utils/logger.h>
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ ModuleId ModuleRegistry::registerModule(
|
|||
ModuleInitializerFactory initializerFactory
|
||||
) {
|
||||
if (!config) {
|
||||
E2D_LOG_ERROR("Cannot register null module config");
|
||||
std::fprintf(stderr, "[ERROR] Cannot register null module config\n");
|
||||
return INVALID_MODULE_ID;
|
||||
}
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ ModuleId ModuleRegistry::registerModule(
|
|||
ModuleInfo info = config->getModuleInfo();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -50,7 +50,6 @@ ModuleId ModuleRegistry::registerModule(
|
|||
modules_[id] = std::move(entry);
|
||||
nameToId_[info.name] = id;
|
||||
|
||||
E2D_LOG_INFO("Registered module '{}' with id {}", info.name, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +64,6 @@ bool ModuleRegistry::unregisterModule(ModuleId id) {
|
|||
|
||||
auto it = modules_.find(id);
|
||||
if (it == modules_.end()) {
|
||||
E2D_LOG_WARN("Module with id {} not found for unregistration", id);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -73,7 +71,6 @@ bool ModuleRegistry::unregisterModule(ModuleId id) {
|
|||
nameToId_.erase(info.name);
|
||||
modules_.erase(it);
|
||||
|
||||
E2D_LOG_INFO("Unregistered module '{}' (id: {})", info.name, id);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -113,11 +110,11 @@ IModuleConfig* ModuleRegistry::getModuleConfigByName(const std::string& name) co
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief 创建模块初始化器
|
||||
* @brief 获取或创建模块初始化器
|
||||
* @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_);
|
||||
|
||||
auto it = modules_.find(id);
|
||||
|
|
@ -125,7 +122,11 @@ UniquePtr<IModuleInitializer> ModuleRegistry::createInitializer(ModuleId id) con
|
|||
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();
|
||||
nameToId_.clear();
|
||||
nextId_ = 1;
|
||||
|
||||
E2D_LOG_INFO("Module registry cleared");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -9,85 +9,47 @@ using json = nlohmann::json;
|
|||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// RenderModuleConfig 实现
|
||||
// ============================================================================
|
||||
static ModuleId s_renderModuleId = INVALID_MODULE_ID;
|
||||
|
||||
ModuleId get_render_module_id() {
|
||||
return s_renderModuleId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 验证渲染配置有效性
|
||||
*
|
||||
* 检查渲染配置的各项参数是否在有效范围内:
|
||||
* - 目标帧率应在 1-240 之间
|
||||
* - 多重采样数应为 0、2、4、8 或 16
|
||||
* - 精灵批处理大小应大于 0
|
||||
*
|
||||
* @return 如果配置有效返回 true
|
||||
*/
|
||||
bool RenderModuleConfig::validate() const {
|
||||
if (targetFPS < 1 || targetFPS > 240) {
|
||||
E2D_LOG_ERROR("Invalid target FPS: {}, must be between 1 and 240", targetFPS);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (multisamples != 0 && multisamples != 2 && multisamples != 4 &&
|
||||
multisamples != 8 && multisamples != 16) {
|
||||
E2D_LOG_ERROR("Invalid multisample count: {}, must be 0, 2, 4, 8 or 16", multisamples);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spriteBatchSize <= 0) {
|
||||
E2D_LOG_ERROR("Invalid sprite batch size: {}, must be greater than 0", spriteBatchSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 应用平台约束
|
||||
*
|
||||
* 根据不同平台的特性调整渲染配置:
|
||||
* - Switch 平台限制 MSAA 最大为 4,禁用 sRGB 帧缓冲
|
||||
* - 其他平台保持用户配置
|
||||
*
|
||||
* @param platform 目标平台类型
|
||||
*/
|
||||
void RenderModuleConfig::applyPlatformConstraints(PlatformType platform) {
|
||||
switch (platform) {
|
||||
case PlatformType::Switch:
|
||||
if (multisamples > 4) {
|
||||
E2D_LOG_WARN("Switch platform limits MSAA to 4x, reducing from {}", multisamples);
|
||||
multisamples = 4;
|
||||
}
|
||||
if (sRGBFramebuffer) {
|
||||
E2D_LOG_WARN("Switch platform does not support sRGB framebuffer, disabling");
|
||||
sRGBFramebuffer = false;
|
||||
}
|
||||
if (targetFPS > 60) {
|
||||
E2D_LOG_WARN("Switch platform target FPS capped at 60");
|
||||
targetFPS = 60;
|
||||
}
|
||||
break;
|
||||
|
||||
case PlatformType::Windows:
|
||||
case PlatformType::Linux:
|
||||
case PlatformType::macOS:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 重置为默认配置
|
||||
*
|
||||
* 将所有配置项恢复为默认值:
|
||||
* - 后端类型:OpenGL
|
||||
* - 垂直同步:启用
|
||||
* - 目标帧率:60
|
||||
* - 多重采样:禁用
|
||||
* - sRGB 帧缓冲:禁用
|
||||
* - 精灵批处理大小:1000
|
||||
*/
|
||||
void RenderModuleConfig::resetToDefaults() {
|
||||
backend = BackendType::OpenGL;
|
||||
vsync = true;
|
||||
|
|
@ -97,19 +59,8 @@ void RenderModuleConfig::resetToDefaults() {
|
|||
spriteBatchSize = 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从 JSON 数据加载配置
|
||||
*
|
||||
* 从 JSON 对象中解析渲染配置参数
|
||||
*
|
||||
* @param jsonData JSON 数据指针(nlohmann::json 对象指针)
|
||||
* @return 加载成功返回 true
|
||||
*/
|
||||
bool RenderModuleConfig::loadFromJson(const void* jsonData) {
|
||||
if (!jsonData) {
|
||||
E2D_LOG_ERROR("Null JSON data provided");
|
||||
return false;
|
||||
}
|
||||
if (!jsonData) return false;
|
||||
|
||||
try {
|
||||
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>();
|
||||
if (backendStr == "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>();
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Render config loaded from JSON");
|
||||
return true;
|
||||
} catch (const json::exception& e) {
|
||||
E2D_LOG_ERROR("Failed to parse render config from JSON: {}", e.what());
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 保存配置到 JSON 数据
|
||||
*
|
||||
* 将当前配置序列化到 JSON 对象
|
||||
*
|
||||
* @param jsonData JSON 数据指针(nlohmann::json 对象指针)
|
||||
* @return 保存成功返回 true
|
||||
*/
|
||||
bool RenderModuleConfig::saveToJson(void* jsonData) const {
|
||||
if (!jsonData) {
|
||||
E2D_LOG_ERROR("Null JSON data provided");
|
||||
return false;
|
||||
}
|
||||
if (!jsonData) return false;
|
||||
|
||||
try {
|
||||
json& j = *static_cast<json*>(jsonData);
|
||||
|
||||
std::string backendStr = "opengl";
|
||||
switch (backend) {
|
||||
case BackendType::OpenGL:
|
||||
backendStr = "opengl";
|
||||
break;
|
||||
default:
|
||||
backendStr = "opengl";
|
||||
break;
|
||||
}
|
||||
|
||||
j["backend"] = backendStr;
|
||||
j["backend"] = "opengl";
|
||||
j["vsync"] = vsync;
|
||||
j["targetFPS"] = targetFPS;
|
||||
j["multisamples"] = multisamples;
|
||||
j["sRGBFramebuffer"] = sRGBFramebuffer;
|
||||
j["spriteBatchSize"] = spriteBatchSize;
|
||||
|
||||
E2D_LOG_INFO("Render config saved to JSON");
|
||||
return true;
|
||||
} catch (const json::exception& e) {
|
||||
E2D_LOG_ERROR("Failed to save render config to JSON: {}", e.what());
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// RenderModuleInitializer 实现
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*
|
||||
* 初始化渲染模块初始化器的成员变量
|
||||
*/
|
||||
RenderModuleInitializer::RenderModuleInitializer()
|
||||
: moduleId_(INVALID_MODULE_ID)
|
||||
, windowModuleId_(INVALID_MODULE_ID)
|
||||
, renderer_(nullptr)
|
||||
, window_(nullptr)
|
||||
, initialized_(false) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*
|
||||
* 确保在销毁时关闭模块
|
||||
*/
|
||||
RenderModuleInitializer::~RenderModuleInitializer() {
|
||||
if (initialized_) {
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取模块依赖列表
|
||||
*
|
||||
* 返回渲染模块依赖的窗口模块标识符列表
|
||||
*
|
||||
* @return 依赖模块标识符列表
|
||||
*/
|
||||
std::vector<ModuleId> RenderModuleInitializer::getDependencies() const {
|
||||
if (windowModuleId_ != INVALID_MODULE_ID) {
|
||||
return {windowModuleId_};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化模块
|
||||
*
|
||||
* 根据配置创建渲染后端实例并初始化渲染器
|
||||
*
|
||||
* @param config 模块配置指针
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool RenderModuleInitializer::initialize(const IModuleConfig* config) {
|
||||
if (initialized_) {
|
||||
E2D_LOG_WARN("Render module already initialized");
|
||||
return true;
|
||||
}
|
||||
if (initialized_) return true;
|
||||
|
||||
if (!config) {
|
||||
E2D_LOG_ERROR("Null config provided for render module initialization");
|
||||
return false;
|
||||
}
|
||||
if (!config) return false;
|
||||
|
||||
const RenderModuleConfig* renderConfig = dynamic_cast<const RenderModuleConfig*>(config);
|
||||
if (!renderConfig) {
|
||||
E2D_LOG_ERROR("Invalid config type for render module");
|
||||
return false;
|
||||
}
|
||||
if (!renderConfig) return false;
|
||||
|
||||
if (!renderConfig->validate()) {
|
||||
E2D_LOG_ERROR("Invalid render module configuration");
|
||||
if (!renderConfig->validate()) return false;
|
||||
|
||||
if (!window_) {
|
||||
E2D_LOG_ERROR("Render module requires window to be set");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -271,35 +152,19 @@ bool RenderModuleInitializer::initialize(const IModuleConfig* config) {
|
|||
return false;
|
||||
}
|
||||
|
||||
IWindow* window = nullptr;
|
||||
if (windowModuleId_ != INVALID_MODULE_ID) {
|
||||
ModuleRegistry& registry = ModuleRegistry::instance();
|
||||
IModuleConfig* windowConfig = registry.getModuleConfig(windowModuleId_);
|
||||
if (windowConfig) {
|
||||
E2D_LOG_INFO("Render module found window module dependency");
|
||||
}
|
||||
if (!renderer_->init(window_)) {
|
||||
E2D_LOG_ERROR("Failed to initialize renderer");
|
||||
renderer_.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
E2D_LOG_INFO("Render module initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 关闭模块
|
||||
*
|
||||
* 销毁渲染后端实例并清理资源
|
||||
*/
|
||||
void RenderModuleInitializer::shutdown() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
if (!initialized_) return;
|
||||
|
||||
if (renderer_) {
|
||||
renderer_->shutdown();
|
||||
|
|
@ -307,7 +172,30 @@ void RenderModuleInitializer::shutdown() {
|
|||
}
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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/config/module_registry.h>
|
||||
#include <extra2d/config/config_manager.h>
|
||||
#include <extra2d/platform/platform_module.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
#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;
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// WindowModuleConfig 实现
|
||||
// ============================================================================
|
||||
static ModuleId s_windowModuleId = INVALID_MODULE_ID;
|
||||
|
||||
/**
|
||||
* @brief 验证窗口配置有效性
|
||||
* 检查窗口尺寸、标题等配置是否合法
|
||||
* @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;
|
||||
ModuleId get_window_module_id() {
|
||||
return s_windowModuleId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
if (!jsonData) {
|
||||
E2D_LOG_ERROR("JSON data is null");
|
||||
return false;
|
||||
}
|
||||
if (!jsonData) return false;
|
||||
|
||||
const json& obj = *static_cast<const json*>(jsonData);
|
||||
|
||||
if (!obj.is_object()) {
|
||||
E2D_LOG_ERROR("JSON data must be an object");
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
try {
|
||||
const json& j = *static_cast<const json*>(jsonData);
|
||||
|
||||
if (j.contains("backend")) {
|
||||
backend = j["backend"].get<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.contains("resizable") && obj["resizable"].is_boolean()) {
|
||||
windowConfig.resizable = obj["resizable"].get<bool>();
|
||||
}
|
||||
|
||||
if (obj.contains("vsync") && obj["vsync"].is_boolean()) {
|
||||
windowConfig.vsync = obj["vsync"].get<bool>();
|
||||
}
|
||||
|
||||
if (obj.contains("multisamples") && obj["multisamples"].is_number_integer()) {
|
||||
windowConfig.multisamples = obj["multisamples"].get<int>();
|
||||
}
|
||||
|
||||
if (obj.contains("msaaSamples") && obj["msaaSamples"].is_number_integer()) {
|
||||
windowConfig.multisamples = obj["msaaSamples"].get<int>();
|
||||
}
|
||||
|
||||
if (obj.contains("centered") && obj["centered"].is_boolean()) {
|
||||
windowConfig.centered = obj["centered"].get<bool>();
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
if (j.contains("title")) {
|
||||
windowConfig.title = j["title"].get<std::string>();
|
||||
}
|
||||
if (j.contains("width")) {
|
||||
windowConfig.width = j["width"].get<int>();
|
||||
}
|
||||
if (j.contains("height")) {
|
||||
windowConfig.height = j["height"].get<int>();
|
||||
}
|
||||
if (j.contains("fullscreen")) {
|
||||
windowConfig.mode = j["fullscreen"].get<bool>() ? WindowMode::Fullscreen : WindowMode::Windowed;
|
||||
}
|
||||
if (j.contains("vsync")) {
|
||||
windowConfig.vsync = j["vsync"].get<bool>();
|
||||
}
|
||||
if (j.contains("resizable")) {
|
||||
windowConfig.resizable = j["resizable"].get<bool>();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (...) {
|
||||
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;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// WindowModuleInitializer 实现
|
||||
// ============================================================================
|
||||
bool WindowModuleConfig::saveToJson(void* jsonData) const {
|
||||
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()
|
||||
: moduleId_(INVALID_MODULE_ID)
|
||||
, window_(nullptr)
|
||||
, initialized_(false) {
|
||||
E2D_LOG_DEBUG("WindowModuleInitializer constructed");
|
||||
, initialized_(false)
|
||||
, backendInitialized_(false) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
* 确保模块正确关闭
|
||||
*/
|
||||
WindowModuleInitializer::~WindowModuleInitializer() {
|
||||
if (initialized_) {
|
||||
shutdown();
|
||||
}
|
||||
E2D_LOG_DEBUG("WindowModuleInitializer destructed");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化模块
|
||||
* 使用 BackendFactory 创建窗口实例
|
||||
* @param config 模块配置指针
|
||||
* @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");
|
||||
bool WindowModuleInitializer::initBackend() {
|
||||
#ifdef E2D_BACKEND_SDL2
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER) != 0) {
|
||||
E2D_LOG_ERROR("Failed to initialize SDL2: {}", SDL_GetError());
|
||||
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);
|
||||
if (!windowConfig) {
|
||||
E2D_LOG_ERROR("Invalid config type for window module");
|
||||
E2D_LOG_ERROR("Invalid window module config");
|
||||
return false;
|
||||
}
|
||||
|
||||
backend_ = windowConfig->backend;
|
||||
windowConfig_ = windowConfig->windowConfig;
|
||||
|
||||
ModuleInfo info = config->getModuleInfo();
|
||||
moduleId_ = info.id;
|
||||
|
||||
const std::string& backend = windowConfig->backend;
|
||||
|
||||
if (!BackendFactory::has(backend)) {
|
||||
E2D_LOG_ERROR("Backend '{}' not available", backend);
|
||||
#ifdef __SWITCH__
|
||||
backend_ = "switch";
|
||||
windowConfig_.mode = WindowMode::Fullscreen;
|
||||
windowConfig_.resizable = false;
|
||||
#endif
|
||||
|
||||
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();
|
||||
if (backends.empty()) {
|
||||
E2D_LOG_ERROR("No backends registered!");
|
||||
shutdownBackend();
|
||||
return false;
|
||||
}
|
||||
std::string backendList;
|
||||
for (const auto& b : backends) {
|
||||
if (!backendList.empty()) backendList += ", ";
|
||||
backendList += b;
|
||||
}
|
||||
E2D_LOG_WARN("Available backends: {}", backendList);
|
||||
backend_ = backends[0];
|
||||
E2D_LOG_WARN("Using fallback backend: {}", backend_);
|
||||
}
|
||||
|
||||
if (!createWindow(backend_, windowConfig_)) {
|
||||
E2D_LOG_ERROR("Failed to create window");
|
||||
shutdownBackend();
|
||||
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);
|
||||
if (!window_) {
|
||||
E2D_LOG_ERROR("Failed to create window for backend: {}", backend);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!window_->create(windowConfig->windowConfig)) {
|
||||
E2D_LOG_ERROR("Failed to create window with given config");
|
||||
window_.reset();
|
||||
|
||||
if (!window_->create(config)) {
|
||||
E2D_LOG_ERROR("Failed to create window");
|
||||
return false;
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
E2D_LOG_INFO("Window module initialized successfully (backend: {}, {}x{})",
|
||||
backend,
|
||||
windowConfig->windowConfig.width,
|
||||
windowConfig->windowConfig.height);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 关闭模块
|
||||
* 销毁窗口实例并重置状态
|
||||
*/
|
||||
void WindowModuleInitializer::shutdown() {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_WARN("Window module not initialized, nothing to shutdown");
|
||||
return;
|
||||
}
|
||||
if (!initialized_) return;
|
||||
|
||||
E2D_LOG_INFO("Window module shutting down");
|
||||
|
||||
if (window_) {
|
||||
window_->destroy();
|
||||
window_.reset();
|
||||
}
|
||||
|
||||
initialized_ = false;
|
||||
moduleId_ = INVALID_MODULE_ID;
|
||||
shutdownBackend();
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,114 +1,177 @@
|
|||
#include <extra2d/utils/logger.h>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef __SWITCH__
|
||||
#include <switch.h>
|
||||
#endif
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// 静态成员定义
|
||||
LogLevel Logger::level_ = LogLevel::Info;
|
||||
bool Logger::initialized_ = false;
|
||||
bool Logger::consoleOutput_ = true;
|
||||
bool Logger::fileOutput_ = false;
|
||||
std::string Logger::logFile_;
|
||||
void *Logger::logFileHandle_ = nullptr;
|
||||
|
||||
/**
|
||||
* @brief 获取日志级别字符串
|
||||
* @param level 日志级别
|
||||
* @return 级别对应的字符串表示
|
||||
*/
|
||||
const char *Logger::getLevelString(LogLevel level) {
|
||||
switch (level) {
|
||||
case LogLevel::Trace:
|
||||
return "TRACE";
|
||||
case LogLevel::Debug:
|
||||
return "DEBUG";
|
||||
case LogLevel::Info:
|
||||
return "INFO ";
|
||||
case LogLevel::Warn:
|
||||
return "WARN ";
|
||||
case LogLevel::Error:
|
||||
return "ERROR";
|
||||
case LogLevel::Fatal:
|
||||
return "FATAL";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
switch (level) {
|
||||
case LogLevel::Trace:
|
||||
return "TRACE";
|
||||
case LogLevel::Debug:
|
||||
return "DEBUG";
|
||||
case LogLevel::Info:
|
||||
return "INFO ";
|
||||
case LogLevel::Warn:
|
||||
return "WARN ";
|
||||
case LogLevel::Error:
|
||||
return "ERROR";
|
||||
case LogLevel::Fatal:
|
||||
return "FATAL";
|
||||
default:
|
||||
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() {
|
||||
if (initialized_) {
|
||||
return;
|
||||
}
|
||||
if (initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置 SDL 日志级别为详细模式(允许所有级别的日志)
|
||||
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_VERBOSE);
|
||||
#ifdef _WIN32
|
||||
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;
|
||||
log(LogLevel::Info, "Logger initialized with SDL2");
|
||||
#ifdef __SWITCH__
|
||||
consoleInit(NULL);
|
||||
#endif
|
||||
|
||||
initialized_ = true;
|
||||
log(LogLevel::Info, "Logger initialized");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 关闭日志系统
|
||||
*
|
||||
* 关闭日志系统并输出关闭日志
|
||||
*/
|
||||
void Logger::shutdown() {
|
||||
if (initialized_) {
|
||||
log(LogLevel::Info, "Logger shutting down");
|
||||
}
|
||||
initialized_ = false;
|
||||
if (initialized_) {
|
||||
log(LogLevel::Info, "Logger shutting down");
|
||||
}
|
||||
|
||||
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) {
|
||||
level_ = level;
|
||||
// 同时设置 SDL 的日志级别
|
||||
if (level != LogLevel::Off) {
|
||||
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION,
|
||||
static_cast<SDL_LogPriority>(level));
|
||||
}
|
||||
level_ = level;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置是否输出到控制台
|
||||
* @param enable true启用控制台输出,false禁用
|
||||
*
|
||||
* 控制日志是否输出到控制台,通过调整SDL日志优先级实现
|
||||
*/
|
||||
void Logger::setConsoleOutput(bool 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_));
|
||||
}
|
||||
consoleOutput_ = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置日志输出到文件
|
||||
* @param filename 日志文件路径
|
||||
*
|
||||
* 配置日志输出到指定文件,空字符串则禁用文件输出
|
||||
*/
|
||||
void Logger::setFileOutput(const std::string &filename) {
|
||||
logFile_ = filename;
|
||||
fileOutput_ = !filename.empty();
|
||||
|
||||
if (fileOutput_) {
|
||||
// SDL2 使用 SDL_LogSetOutputFunction 可以重定向日志输出
|
||||
// 这里我们记录文件路径,实际文件输出可以通过自定义回调实现
|
||||
log(LogLevel::Info, "File output configured: {}", filename);
|
||||
}
|
||||
if (logFileHandle_) {
|
||||
fclose(static_cast<FILE *>(logFileHandle_));
|
||||
logFileHandle_ = nullptr;
|
||||
}
|
||||
|
||||
logFile_ = 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
|
||||
|
|
|
|||
|
|
@ -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