refactor(module): 重构模块系统并添加核心模块实现

重构模块注册表结构以支持初始化器实例缓存,添加配置、日志、平台和渲染等核心模块实现
优化日志系统移除SDL依赖,改进跨平台支持
调整模块初始化流程,增强模块间依赖管理
This commit is contained in:
ChestnutYueyue 2026-02-15 10:08:44 +08:00
parent 38148a6c54
commit 8f7f1612eb
17 changed files with 1257 additions and 1471 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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
*/
RenderBackend* getRenderer() const { return renderer_.get(); }
void setModuleId(ModuleId id) { moduleId_ = id; }
void setWindow(IWindow* window) { window_ = window; }
/**
* @brief
* @param windowModuleId
*/
void setWindowModuleId(ModuleId windowModuleId) { windowModuleId_ = windowModuleId; }
RenderBackend* getRenderer() const { return renderer_.get(); }
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

View File

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

View File

@ -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";
WindowConfigData windowConfig;
/**
* @brief
* @return
*/
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"; }
std::string getConfigSectionName() const override {
return "window";
}
/**
* @brief
* @return true
*/
bool validate() const override;
bool validate() const override {
return windowConfig.width > 0 && windowConfig.height > 0;
}
/**
* @brief
*
* @param platform
*/
void applyPlatformConstraints(PlatformType platform) override;
void resetToDefaults() override {
backend = "sdl2";
windowConfig = WindowConfigData{};
}
/**
* @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 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

View File

@ -6,30 +6,20 @@
#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;
@ -39,7 +29,6 @@ template <typename T> inline std::string to_string_arg(const T &value) {
} 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));
@ -54,12 +43,11 @@ template <typename T> inline std::string to_string_arg(const T &value) {
}
}
// 格式化基础情况:没有更多参数
inline std::string format_impl(const char *fmt) {
std::string result;
while (*fmt) {
if (*fmt == '{' && *(fmt + 1) == '}') {
result += "{}"; // 无参数可替换,保留原样
result += "{}";
fmt += 2;
} else {
result += *fmt;
@ -69,26 +57,22 @@ inline std::string format_impl(const char *fmt) {
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) {
@ -103,7 +87,6 @@ inline std::string format_impl(const char *fmt, const T &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) {
@ -138,101 +121,51 @@ inline std::string format_impl(const char *fmt, const T &first,
} // namespace detail
// 顶层格式化函数
template <typename... Args>
inline std::string e2d_format(const char *fmt, const Args &...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();
/**
* @brief
*/
static void shutdown();
/**
* @brief
* @param level
*/
static void setLevel(LogLevel level);
/**
* @brief
* @param enable
*/
static void setConsoleOutput(bool enable);
/**
* @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());
outputLog(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);
outputLog(level, msg);
}
private:
static LogLevel level_; // 当前日志级别
static bool initialized_; // 是否已初始化
static bool consoleOutput_; // 是否输出到控制台
static bool fileOutput_; // 是否输出到文件
static std::string logFile_; // 日志文件路径
/**
* @brief
* @param level
* @return
*/
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);
// ============================================================================
// 日志宏
// ============================================================================
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(...) \
@ -253,7 +186,6 @@ private:
#define E2D_LOG_FATAL(...) \
::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__)

View File

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

View File

@ -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;
auto* renderInit = ModuleRegistry::instance().getInitializer(get_render_module_id());
if (renderInit) {
auto* renderModule = dynamic_cast<RenderModuleInitializer*>(renderInit);
if (renderModule) {
renderModule->setWindow(window_);
if (platform == PlatformType::Switch) {
winConfig.mode = WindowMode::Fullscreen;
winConfig.resizable = false;
}
if (!window_->create(winConfig)) {
E2D_LOG_ERROR("Failed to create window");
auto* renderConfig = ModuleRegistry::instance().getModuleConfig(get_render_module_id());
if (renderConfig && !renderInit->initialize(renderConfig)) {
return false;
}
renderer_ = RenderBackend::create(appConfig.render.backend);
if (!renderer_ || !renderer_->init(window_.get())) {
E2D_LOG_ERROR("Failed to initialize renderer");
window_->destroy();
return false;
}
}
sceneManager_ = makeUnique<SceneManager>();
@ -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_;
}

View File

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

View File

@ -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");
}
/**

View File

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

View File

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

View File

@ -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;
try {
const json& j = *static_cast<const json*>(jsonData);
if (j.contains("backend")) {
backend = j["backend"].get<std::string>();
}
const json& obj = *static_cast<const json*>(jsonData);
if (!obj.is_object()) {
E2D_LOG_ERROR("JSON data must be an object");
return false;
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>();
}
if (obj.contains("title") && obj["title"].is_string()) {
windowConfig.title = obj["title"].get<std::string>();
}
if (obj.contains("width") && obj["width"].is_number_integer()) {
windowConfig.width = obj["width"].get<int>();
}
if (obj.contains("height") && obj["height"].is_number_integer()) {
windowConfig.height = obj["height"].get<int>();
}
if (obj.contains("fullscreen") && obj["fullscreen"].is_boolean()) {
windowConfig.mode = obj["fullscreen"].get<bool>() ? WindowMode::Fullscreen : WindowMode::Windowed;
}
if (obj.contains("mode") && obj["mode"].is_string()) {
std::string modeStr = obj["mode"].get<std::string>();
if (modeStr == "fullscreen") {
windowConfig.mode = WindowMode::Fullscreen;
} else if (modeStr == "borderless") {
windowConfig.mode = WindowMode::Borderless;
} else {
windowConfig.mode = WindowMode::Windowed;
}
}
if (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;
} catch (...) {
return false;
}
}
/**
* @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 (!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;
}
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 实现
// ============================================================================
/**
* @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;
}
ModuleInfo info = config->getModuleInfo();
moduleId_ = info.id;
backend_ = windowConfig->backend;
windowConfig_ = windowConfig->windowConfig;
const std::string& backend = windowConfig->backend;
#ifdef __SWITCH__
backend_ = "switch";
windowConfig_.mode = WindowMode::Fullscreen;
windowConfig_.resizable = false;
#endif
if (!BackendFactory::has(backend)) {
E2D_LOG_ERROR("Backend '{}' not available", backend);
if (!initBackend()) {
return false;
}
#ifdef E2D_BACKEND_SDL2
extern void initSDL2Backend();
initSDL2Backend();
#endif
if (!BackendFactory::has(backend_)) {
E2D_LOG_ERROR("Backend '{}' not available", backend_);
auto backends = BackendFactory::backends();
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;
backend_ = backends[0];
E2D_LOG_WARN("Using fallback backend: {}", backend_);
}
E2D_LOG_WARN("Available backends: {}", backendList);
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

View File

@ -1,19 +1,24 @@
#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:
@ -33,81 +38,139 @@ const char *Logger::getLevelString(LogLevel level) {
}
}
/**
* @brief
*
* SDL日志级别为详细模式
*/
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);
}
}
void Logger::init() {
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
#ifdef __SWITCH__
consoleInit(NULL);
#endif
initialized_ = true;
log(LogLevel::Info, "Logger initialized with SDL2");
log(LogLevel::Info, "Logger initialized");
}
/**
* @brief
*
*
*/
void Logger::shutdown() {
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));
}
}
/**
* @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_));
}
}
/**
* @brief
* @param filename
*
*
*/
void Logger::setFileOutput(const std::string &filename) {
if (logFileHandle_) {
fclose(static_cast<FILE *>(logFileHandle_));
logFileHandle_ = nullptr;
}
logFile_ = filename;
fileOutput_ = !filename.empty();
if (fileOutput_) {
// SDL2 使用 SDL_LogSetOutputFunction 可以重定向日志输出
// 这里我们记录文件路径,实际文件输出可以通过自定义回调实现
log(LogLevel::Info, "File output configured: {}", filename);
#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);
}
}
}

View File

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