feat: 重构模块系统并添加事件监听便捷方法

重构整个模块系统,移除旧的模块注册和初始化机制,改为直接继承 Module 基类的方式。新增 Node 类的事件监听便捷方法,简化事件处理流程。

主要变更包括:
1. 移除 module_config.h、module_initializer.h 和 module_registry.h 等旧模块系统文件
2. 新增 core/module.h 作为新模块系统基础
3. 为 Node 类添加 addListener/removeListener 等事件便捷方法
4. 将原有模块(Logger, Config, Platform等)重构为继承 Module 的新实现
5. 更新 Application 类以支持新的模块管理方式
6. 修改 hello_module 示例展示新模块系统用法
This commit is contained in:
ChestnutYueyue 2026-02-15 20:13:18 +08:00
parent 6273f3235d
commit f8a7fab2e7
42 changed files with 2424 additions and 3854 deletions

View File

@ -1,12 +1,14 @@
#pragma once #pragma once
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <extra2d/core/module.h>
#include <extra2d/core/service_locator.h> #include <extra2d/core/service_locator.h>
#include <extra2d/config/app_config.h> #include <extra2d/config/app_config.h>
#include <extra2d/config/config_manager.h> #include <extra2d/config/config_manager.h>
#include <extra2d/config/module_config.h>
#include <extra2d/platform/iwindow.h> #include <extra2d/platform/iwindow.h>
#include <string> #include <string>
#include <vector>
#include <initializer_list>
namespace extra2d { namespace extra2d {
@ -28,6 +30,18 @@ public:
Application(const Application&) = delete; Application(const Application&) = delete;
Application& operator=(const Application&) = delete; Application& operator=(const Application&) = delete;
/**
* @brief
* @param m
*/
void use(Module& m);
/**
* @brief
* @param modules
*/
void use(std::initializer_list<Module*> modules);
/** /**
* @brief 使 * @brief 使
* @return true * @return true
@ -188,10 +202,20 @@ private:
~Application(); ~Application();
/** /**
* @brief * @brief
* @return true * @return true
*/ */
bool initModules(); bool initCoreModules();
/**
* @brief
*/
void setupAllModules();
/**
* @brief
*/
void destroyAllModules();
/** /**
* @brief * @brief
@ -213,6 +237,7 @@ private:
*/ */
void render(); void render();
std::vector<Module*> modules_;
IWindow* window_ = nullptr; IWindow* window_ = nullptr;
bool initialized_ = false; bool initialized_ = false;

View File

@ -1,70 +0,0 @@
#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

@ -1,103 +0,0 @@
#pragma once
#include <extra2d/config/platform_config.h>
#include <extra2d/core/types.h>
#include <string>
namespace extra2d {
/**
* @brief
*/
using ModuleId = uint32_t;
/**
* @brief
*/
constexpr ModuleId INVALID_MODULE_ID = 0;
/**
* @brief
*
*/
enum class ModulePriority : int {
Core = 0, ///< 核心模块(最先初始化)
Platform = 100, ///< 平台相关模块
Graphics = 200, ///< 图形渲染模块
Audio = 300, ///< 音频模块
Input = 400, ///< 输入模块
Resource = 500, ///< 资源管理模块
Game = 1000, ///< 游戏逻辑模块
User = 2000 ///< 用户自定义模块
};
/**
* @brief
*
*/
struct ModuleInfo {
ModuleId id = INVALID_MODULE_ID; ///< 模块唯一标识符
std::string name; ///< 模块名称
std::string version; ///< 模块版本号
ModulePriority priority = ModulePriority::User; ///< 模块优先级
bool enabled = true; ///< 是否启用
};
/**
* @brief
*
*/
class IModuleConfig {
public:
/**
* @brief
*/
virtual ~IModuleConfig() = default;
/**
* @brief
* @return
*/
virtual ModuleInfo getModuleInfo() const = 0;
/**
* @brief
*
* @return
*/
virtual std::string getConfigSectionName() const = 0;
/**
* @brief
* @return true
*/
virtual bool validate() const { return true; }
/**
* @brief
*
* @param platform
*/
virtual void applyPlatformConstraints(PlatformType platform) { }
/**
* @brief
*/
virtual void resetToDefaults() = 0;
/**
* @brief JSON
* @param jsonData JSON
* @return true
*/
virtual bool loadFromJson(const void* jsonData) { return true; }
/**
* @brief JSON
* @param jsonData JSON
* @return true
*/
virtual bool saveToJson(void* jsonData) const { return true; }
};
} // namespace extra2d

View File

@ -1,63 +0,0 @@
#pragma once
#include <extra2d/config/module_config.h>
#include <functional>
#include <vector>
namespace extra2d {
/**
* @brief
*
*/
class IModuleInitializer {
public:
/**
* @brief
*/
virtual ~IModuleInitializer() = default;
/**
* @brief
* @return
*/
virtual ModuleId getModuleId() const = 0;
/**
* @brief
* @return
*/
virtual ModulePriority getPriority() const = 0;
/**
* @brief
*
* @return
*/
virtual std::vector<ModuleId> getDependencies() const { return {}; }
/**
* @brief
* @param config
* @return true
*/
virtual bool initialize(const IModuleConfig* config) = 0;
/**
* @brief
*/
virtual void shutdown() = 0;
/**
* @brief
* @return true
*/
virtual bool isInitialized() const = 0;
};
/**
* @brief
*/
using ModuleInitializerFactory = std::function<UniquePtr<IModuleInitializer>()>;
} // namespace extra2d

View File

@ -1,156 +0,0 @@
#pragma once
#include <extra2d/config/module_config.h>
#include <extra2d/config/module_initializer.h>
#include <unordered_map>
#include <vector>
#include <mutex>
namespace extra2d {
/**
* @brief
*
*/
struct ModuleEntry {
ModuleId id; ///< 模块标识符
UniquePtr<IModuleConfig> config; ///< 模块配置
ModuleInitializerFactory initializerFactory;///< 初始化器工厂函数
UniquePtr<IModuleInitializer> initializer; ///< 初始化器实例
bool initialized = false; ///< 是否已初始化
};
/**
* @brief
*
*/
class ModuleRegistry {
public:
/**
* @brief
* @return
*/
static ModuleRegistry& instance();
/**
* @brief
*/
ModuleRegistry(const ModuleRegistry&) = delete;
/**
* @brief
*/
ModuleRegistry& operator=(const ModuleRegistry&) = delete;
/**
* @brief
* @param config
* @param initializerFactory
* @return
*/
ModuleId registerModule(
UniquePtr<IModuleConfig> config,
ModuleInitializerFactory initializerFactory = nullptr
);
/**
* @brief
* @param id
* @return true
*/
bool unregisterModule(ModuleId id);
/**
* @brief
* @param id
* @return nullptr
*/
IModuleConfig* getModuleConfig(ModuleId id) const;
/**
* @brief
* @param name
* @return nullptr
*/
IModuleConfig* getModuleConfigByName(const std::string& name) const;
/**
* @brief
* @param id
* @return nullptr
*/
IModuleInitializer* getInitializer(ModuleId id);
/**
* @brief
* @return
*/
std::vector<ModuleId> getAllModules() const;
/**
* @brief
*
* @return
*/
std::vector<ModuleId> getInitializationOrder() const;
/**
* @brief
* @param id
* @return true
*/
bool hasModule(ModuleId id) const;
/**
* @brief
*/
void clear();
/**
* @brief
* @return
*/
size_t size() const { return modules_.size(); }
private:
/**
* @brief
*/
ModuleRegistry() = default;
/**
* @brief
*/
~ModuleRegistry() = default;
/**
* @brief
* @return
*/
ModuleId generateId();
std::unordered_map<ModuleId, ModuleEntry> modules_; ///< 模块注册表
std::unordered_map<std::string, ModuleId> nameToId_; ///< 名称到标识符映射
mutable std::mutex mutex_; ///< 线程安全互斥锁
ModuleId nextId_ = 1; ///< 下一个可用标识符
};
/**
* @brief
* 使
*
* @example
* E2D_REGISTER_MODULE(MyModuleConfig, MyModuleInitializer)
*/
#define E2D_REGISTER_MODULE(ConfigClass, InitializerClass) \
namespace { \
static const ::extra2d::ModuleId E2D_ANONYMOUS_VAR(module_id_) = \
::extra2d::ModuleRegistry::instance().registerModule( \
::extra2d::makeUnique<ConfigClass>(), \
[]() -> ::extra2d::UniquePtr<::extra2d::IModuleInitializer> { \
return ::extra2d::makeUnique<InitializerClass>(); \
} \
); \
}
} // namespace extra2d

View File

@ -0,0 +1,229 @@
#pragma once
#include <extra2d/core/types.h>
#include <vector>
namespace extra2d {
class Module;
class UpdateContext;
class RenderContext;
class EventContext;
/**
* @brief
*
*/
class ModuleContext {
public:
/**
* @brief
*/
virtual ~ModuleContext() = default;
/**
* @brief
*/
void next();
/**
* @brief
* @return true
*/
bool isDone() const { return index_ >= static_cast<int>(modules_.size()); }
protected:
/**
* @brief
* @param modules
*/
ModuleContext(std::vector<Module*>& modules);
/**
* @brief
* @param m
*/
virtual void handle(Module* m) = 0;
std::vector<Module*>& modules_;
int index_ = 0;
};
/**
* @brief
*
*/
class UpdateContext : public ModuleContext {
public:
/**
* @brief
* @param modules
* @param deltaTime
*/
UpdateContext(std::vector<Module*>& modules, float deltaTime);
/**
* @brief
* @return
*/
float getDeltaTime() const { return deltaTime_; }
protected:
void handle(Module* m) override;
private:
float deltaTime_;
};
/**
* @brief
*
*/
class RenderContext : public ModuleContext {
public:
/**
* @brief
*/
enum class Phase {
Before, ///< 渲染前
On, ///< 渲染时
After ///< 渲染后
};
/**
* @brief
* @param modules
* @param phase
*/
RenderContext(std::vector<Module*>& modules, Phase phase);
/**
* @brief
* @return
*/
Phase getPhase() const { return phase_; }
protected:
void handle(Module* m) override;
private:
Phase phase_;
};
/**
* @brief
*
*/
class EventContext : public ModuleContext {
public:
/**
* @brief
* @param modules
*/
EventContext(std::vector<Module*>& modules);
protected:
void handle(Module* m) override;
};
/**
* @brief
*
*/
class Module {
public:
/**
* @brief
*/
virtual ~Module() = default;
/**
* @brief
* Application::run()
*/
virtual void setupModule() {}
/**
* @brief
* Application
*/
virtual void destroyModule() {}
/**
* @brief
*
* @param ctx
*/
virtual void onUpdate(UpdateContext& ctx) { ctx.next(); }
/**
* @brief
*
* @param ctx
*/
virtual void beforeRender(RenderContext& ctx) { ctx.next(); }
/**
* @brief
*
* @param ctx
*/
virtual void onRender(RenderContext& ctx) { ctx.next(); }
/**
* @brief
*
* @param ctx
*/
virtual void afterRender(RenderContext& ctx) { ctx.next(); }
/**
* @brief
*
* @param ctx
*/
virtual void handleEvent(EventContext& ctx) { ctx.next(); }
/**
* @brief
* @return
*/
virtual const char* getName() const = 0;
/**
* @brief
*
* @return
*/
virtual int getPriority() const { return 0; }
/**
* @brief
* @return true
*/
bool isInitialized() const { return initialized_; }
protected:
friend class Application;
/**
* @brief
* @param initialized
*/
void setInitialized(bool initialized) { initialized_ = initialized; }
bool initialized_ = false;
};
/**
* @brief
*
* @param a a
* @param b b
* @return a优先级小于b返回true
*/
inline bool modulePriorityCompare(Module* a, Module* b) {
return a->getPriority() < b->getPriority();
}
} // namespace extra2d

View File

@ -7,24 +7,28 @@
#include <extra2d/core/color.h> #include <extra2d/core/color.h>
#include <extra2d/core/math_types.h> #include <extra2d/core/math_types.h>
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <extra2d/core/module.h>
// Config // Config
#include <extra2d/config/app_config.h> #include <extra2d/config/app_config.h>
#include <extra2d/config/config_loader.h> #include <extra2d/config/config_loader.h>
#include <extra2d/config/config_manager.h> #include <extra2d/config/config_manager.h>
#include <extra2d/config/module_config.h>
#include <extra2d/config/module_initializer.h>
#include <extra2d/config/module_registry.h>
#include <extra2d/config/platform_config.h> #include <extra2d/config/platform_config.h>
#include <extra2d/config/platform_detector.h> #include <extra2d/config/platform_detector.h>
// Modules
#include <extra2d/modules/config_module.h>
#include <extra2d/modules/logger_module.h>
#include <extra2d/modules/platform_module.h>
#include <extra2d/modules/window_module.h>
#include <extra2d/modules/input_module.h>
#include <extra2d/modules/render_module.h>
// Platform // Platform
#include <extra2d/platform/iinput.h> #include <extra2d/platform/iinput.h>
#include <extra2d/platform/iwindow.h> #include <extra2d/platform/iwindow.h>
#include <extra2d/platform/keys.h> #include <extra2d/platform/keys.h>
#include <extra2d/platform/input_module.h> #include <extra2d/platform/window_config.h>
#include <extra2d/platform/platform_module.h>
#include <extra2d/platform/window_module.h>
// Graphics // Graphics
#include <extra2d/graphics/camera.h> #include <extra2d/graphics/camera.h>

View File

@ -1,66 +0,0 @@
#pragma once
#include <extra2d/config/module_config.h>
#include <extra2d/config/module_initializer.h>
#include <extra2d/graphics/render_config.h>
#include <extra2d/graphics/render_backend.h>
#include <extra2d/core/types.h>
namespace extra2d {
class RenderModuleConfig : public IModuleConfig {
public:
BackendType backend = BackendType::OpenGL;
bool vsync = true;
int targetFPS = 60;
int multisamples = 0;
bool sRGBFramebuffer = false;
int spriteBatchSize = 1000;
ModuleInfo getModuleInfo() const override {
ModuleInfo info;
info.name = "Render";
info.version = "1.0.0";
info.priority = ModulePriority::Graphics;
info.enabled = true;
return info;
}
std::string getConfigSectionName() const override { return "render"; }
bool validate() const override;
void applyPlatformConstraints(PlatformType platform) override;
void resetToDefaults() override;
bool loadFromJson(const void* jsonData) override;
bool saveToJson(void* jsonData) const override;
};
class RenderModuleInitializer : public IModuleInitializer {
public:
RenderModuleInitializer();
~RenderModuleInitializer() override;
ModuleId getModuleId() const override { return moduleId_; }
ModulePriority getPriority() const override { return ModulePriority::Graphics; }
std::vector<ModuleId> getDependencies() const override;
bool initialize(const IModuleConfig* config) override;
void shutdown() override;
bool isInitialized() const override { return initialized_; }
void setModuleId(ModuleId id) { moduleId_ = id; }
void setWindow(IWindow* window) { window_ = window; }
RenderBackend* getRenderer() const { return renderer_.get(); }
private:
ModuleId moduleId_ = 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,71 @@
#pragma once
#include <extra2d/core/module.h>
#include <extra2d/config/app_config.h>
#include <extra2d/config/config_manager.h>
#include <string>
namespace extra2d {
/**
* @brief
*
*/
class ConfigModule : public Module {
public:
/**
* @brief
*/
ConfigModule();
/**
* @brief
*/
~ConfigModule() override;
/**
* @brief
* @return
*/
const char* getName() const override { return "Config"; }
/**
* @brief
* @return
*/
int getPriority() const override { return 0; }
/**
* @brief
*/
void setupModule() override;
/**
* @brief
*/
void destroyModule() override;
/**
* @brief
* @param config
*/
void setAppConfig(const AppConfig& config) { appConfig_ = config; }
/**
* @brief
* @param path
*/
void setConfigPath(const std::string& path) { configPath_ = path; }
/**
* @brief
* @return
*/
ConfigManager& getManager() { return ConfigManager::instance(); }
private:
AppConfig appConfig_;
std::string configPath_;
};
} // namespace extra2d

View File

@ -0,0 +1,78 @@
#pragma once
#include <extra2d/core/module.h>
#include <extra2d/input/input_config.h>
#include <extra2d/platform/iinput.h>
#include <extra2d/platform/iwindow.h>
namespace extra2d {
/**
* @brief
*
*/
class InputModule : public Module {
public:
/**
* @brief
*/
InputModule();
/**
* @brief
*/
~InputModule() override;
/**
* @brief
* @return
*/
const char* getName() const override { return "Input"; }
/**
* @brief
* @return
*/
int getPriority() const override { return 30; }
/**
* @brief
*/
void setupModule() override;
/**
* @brief
*/
void destroyModule() override;
/**
* @brief
* @param ctx
*/
void onUpdate(UpdateContext& ctx) override;
/**
* @brief
* @param config
*/
void setInputConfig(const InputConfigData& config) { config_ = config; }
/**
* @brief
* @return
*/
IInput* getInput() const { return input_; }
/**
* @brief
* @param window
*/
void setWindow(IWindow* window) { window_ = window; }
private:
IWindow* window_ = nullptr;
IInput* input_ = nullptr;
InputConfigData config_;
};
} // namespace extra2d

View File

@ -0,0 +1,75 @@
#pragma once
#include <extra2d/core/module.h>
#include <extra2d/utils/logger.h>
#include <string>
namespace extra2d {
/**
* @brief
*
*/
class LoggerModule : public Module {
public:
/**
* @brief
*/
LoggerModule();
/**
* @brief
*/
~LoggerModule() override;
/**
* @brief
* @return
*/
const char* getName() const override { return "Logger"; }
/**
* @brief
* @return
*/
int getPriority() const override { return -1; }
/**
* @brief
*/
void setupModule() override;
/**
* @brief
*/
void destroyModule() override;
/**
* @brief
* @param level
*/
void setLogLevel(LogLevel level) { logLevel_ = level; }
/**
* @brief
* @param enabled
*/
void setConsoleOutput(bool enabled) { consoleOutput_ = enabled; }
/**
* @brief
* @param filePath
*/
void setFileOutput(const std::string& filePath) {
fileOutput_ = true;
logFilePath_ = filePath;
}
private:
LogLevel logLevel_ = LogLevel::Info;
bool consoleOutput_ = true;
bool fileOutput_ = false;
std::string logFilePath_;
};
} // namespace extra2d

View File

@ -0,0 +1,81 @@
#pragma once
#include <extra2d/core/module.h>
#include <extra2d/config/platform_config.h>
namespace extra2d {
/**
* @brief
*
*/
class PlatformModule : public Module {
public:
/**
* @brief
*/
PlatformModule();
/**
* @brief
*/
~PlatformModule() override;
/**
* @brief
* @return
*/
const char* getName() const override { return "Platform"; }
/**
* @brief
* @return
*/
int getPriority() const override { return 10; }
/**
* @brief
*/
void setupModule() override;
/**
* @brief
*/
void destroyModule() override;
/**
* @brief
* @param platform
*/
void setTargetPlatform(PlatformType platform) { targetPlatform_ = platform; }
/**
* @brief
* @return
*/
PlatformType getPlatform() const { return resolvedPlatform_; }
/**
* @brief
* @return
*/
PlatformConfig* getPlatformConfig() const { return platformConfig_.get(); }
private:
/**
* @brief Switch
* @return true
*/
bool initSwitch();
/**
* @brief Switch
*/
void shutdownSwitch();
PlatformType targetPlatform_ = PlatformType::Auto;
PlatformType resolvedPlatform_ = PlatformType::Windows;
UniquePtr<PlatformConfig> platformConfig_;
};
} // namespace extra2d

View File

@ -0,0 +1,116 @@
#pragma once
#include <extra2d/core/module.h>
#include <extra2d/graphics/render_config.h>
#include <extra2d/graphics/render_backend.h>
namespace extra2d {
/**
* @brief
*/
struct RenderModuleConfig {
BackendType backend = BackendType::OpenGL;
bool vsync = true;
int targetFPS = 60;
int multisamples = 0;
bool sRGBFramebuffer = false;
int spriteBatchSize = 1000;
/**
* @brief
* @return true
*/
bool validate() const {
if (targetFPS < 1 || targetFPS > 240) {
return false;
}
if (multisamples != 0 && multisamples != 2 && multisamples != 4 &&
multisamples != 8 && multisamples != 16) {
return false;
}
if (spriteBatchSize <= 0) {
return false;
}
return true;
}
};
/**
* @brief
*
*/
class RenderModule : public Module {
public:
/**
* @brief
*/
RenderModule();
/**
* @brief
*/
~RenderModule() override;
/**
* @brief
* @return
*/
const char* getName() const override { return "Render"; }
/**
* @brief
* @return
*/
int getPriority() const override { return 40; }
/**
* @brief
*/
void setupModule() override;
/**
* @brief
*/
void destroyModule() override;
/**
* @brief
* @param ctx
*/
void beforeRender(RenderContext& ctx) override;
/**
* @brief
* @param ctx
*/
void afterRender(RenderContext& ctx) override;
/**
* @brief
* @param config
*/
void setRenderConfig(const RenderModuleConfig& config) { config_ = config; }
/**
* @brief
* @param window
*/
void setWindow(IWindow* window) { window_ = window; }
/**
* @brief
* @return
*/
RenderBackend* getRenderer() const { return renderer_.get(); }
private:
IWindow* window_ = nullptr;
UniquePtr<RenderBackend> renderer_;
RenderModuleConfig config_;
};
} // namespace extra2d

View File

@ -0,0 +1,83 @@
#pragma once
#include <extra2d/core/module.h>
#include <extra2d/platform/window_config.h>
#include <extra2d/platform/iwindow.h>
namespace extra2d {
/**
* @brief
*
*/
class WindowModule : public Module {
public:
/**
* @brief
*/
WindowModule();
/**
* @brief
*/
~WindowModule() override;
/**
* @brief
* @return
*/
const char* getName() const override { return "Window"; }
/**
* @brief
* @return
*/
int getPriority() const override { return 20; }
/**
* @brief
*/
void setupModule() override;
/**
* @brief
*/
void destroyModule() override;
/**
* @brief
* @param config
*/
void setWindowConfig(const WindowConfigData& config) { windowConfig_ = config; }
/**
* @brief
* @return
*/
IWindow* getWindow() const { return window_.get(); }
private:
/**
* @brief SDL2
* @return true
*/
bool initSDL2();
/**
* @brief SDL2
*/
void shutdownSDL2();
/**
* @brief
* @param config
* @return true
*/
bool createWindow(const WindowConfigData& config);
bool sdl2Initialized_ = false;
WindowConfigData windowConfig_;
UniquePtr<IWindow> window_;
};
} // namespace extra2d

View File

@ -1,129 +0,0 @@
#pragma once
#include <extra2d/config/module_config.h>
#include <extra2d/config/module_initializer.h>
#include <extra2d/input/input_config.h>
#include <extra2d/platform/iinput.h>
#include <extra2d/core/types.h>
namespace extra2d {
/**
* @file input_module.h
* @brief
*
*
*
*/
/**
* @brief
* IModuleConfig
*/
class InputModuleConfig : public IModuleConfig {
public:
InputConfigData inputConfig;
/**
* @brief
* @return
*/
ModuleInfo getModuleInfo() const override {
ModuleInfo info;
info.id = 0;
info.name = "Input";
info.version = "1.0.0";
info.priority = ModulePriority::Input;
info.enabled = true;
return info;
}
/**
* @brief
* @return
*/
std::string getConfigSectionName() const override { return "input"; }
/**
* @brief
* @return true
*/
bool validate() const override;
/**
* @brief
* @param platform
*/
void applyPlatformConstraints(PlatformType platform) override;
/**
* @brief
*/
void resetToDefaults() override {
inputConfig = InputConfigData{};
}
/**
* @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 InputModuleInitializer : public IModuleInitializer {
public:
InputModuleInitializer();
~InputModuleInitializer() override;
ModuleId getModuleId() const override { return moduleId_; }
ModulePriority getPriority() const override { return ModulePriority::Input; }
std::vector<ModuleId> getDependencies() const override;
bool initialize(const IModuleConfig* config) override;
void shutdown() override;
bool isInitialized() const override { return initialized_; }
void setModuleId(ModuleId id) { moduleId_ = id; }
/**
* @brief
* @return
*/
IInput* getInput() const { return input_; }
/**
* @brief
*
*/
void update();
private:
ModuleId moduleId_ = INVALID_MODULE_ID;
IInput* input_ = nullptr;
bool initialized_ = false;
InputConfigData config_;
};
/**
* @brief
* @return
*/
ModuleId get_input_module_id();
/**
* @brief
*/
void register_input_module();
}

View File

@ -1,72 +0,0 @@
#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

@ -1,195 +0,0 @@
#pragma once
#include <extra2d/config/module_config.h>
#include <extra2d/config/module_initializer.h>
#include <extra2d/platform/window_config.h>
#include <extra2d/platform/iwindow.h>
#include <string>
namespace extra2d {
/**
* @file window_module.h
* @brief
*
* 使 SDL2
* - Windows
* - Linux
* - macOS
* - Nintendo Switch
*/
/**
* @brief
* IModuleConfig
*/
class WindowModuleConfig : public IModuleConfig {
public:
WindowConfigData windowConfig;
/**
* @brief
* @return
*/
ModuleInfo getModuleInfo() const override {
ModuleInfo info;
info.id = 0;
info.name = "Window";
info.version = "1.0.0";
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 {
return windowConfig.width > 0 && windowConfig.height > 0;
}
/**
* @brief
* @param platform
*/
void applyPlatformConstraints(PlatformType platform) override;
/**
* @brief
*/
void resetToDefaults() override {
windowConfig = WindowConfigData{};
}
/**
* @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::Core; }
/**
* @brief
* @return
*/
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
* @param id
*/
void setModuleId(ModuleId id) { moduleId_ = id; }
/**
* @brief
* @param config
*/
void setWindowConfig(const WindowConfigData& config) { windowConfig_ = config; }
/**
* @brief
* @return
*/
IWindow* getWindow() const { return window_.get(); }
private:
/**
* @brief SDL2
* @return true
*/
bool initSDL2();
/**
* @brief SDL2
*/
void shutdownSDL2();
/**
* @brief
* @param config
* @return true
*/
bool createWindow(const WindowConfigData& config);
ModuleId moduleId_ = INVALID_MODULE_ID;
bool initialized_ = false;
bool sdl2Initialized_ = false;
WindowConfigData windowConfig_;
UniquePtr<IWindow> window_;
};
/**
* @brief
* @return
*/
ModuleId get_window_module_id();
/**
* @brief
*/
void register_window_module();
}

View File

@ -151,6 +151,31 @@ public:
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
EventDispatcher &getEventDispatcher() { return eventDispatcher_; } EventDispatcher &getEventDispatcher() { return eventDispatcher_; }
/**
* @brief 便
* @param type
* @param callback
* @return ID
*/
ListenerId addListener(EventType type, EventDispatcher::EventCallback callback);
/**
* @brief
* @param id ID
*/
void removeListener(ListenerId id);
/**
* @brief
* @param type
*/
void removeAllListeners(EventType type);
/**
* @brief
*/
void removeAllListeners();
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 内部方法 // 内部方法
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------

View File

@ -1,66 +0,0 @@
#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,19 +1,17 @@
#include <extra2d/app/application.h> #include <extra2d/app/application.h>
#include <extra2d/config/config_module.h>
#include <extra2d/config/module_registry.h>
#include <extra2d/graphics/render_config.h>
#include <extra2d/graphics/render_module.h>
#include <extra2d/graphics/vram_manager.h> #include <extra2d/graphics/vram_manager.h>
#include <extra2d/platform/iinput.h> #include <extra2d/modules/config_module.h>
#include <extra2d/platform/input_module.h> #include <extra2d/modules/input_module.h>
#include <extra2d/platform/platform_init_module.h> #include <extra2d/modules/platform_module.h>
#include <extra2d/platform/window_module.h> #include <extra2d/modules/render_module.h>
#include <extra2d/modules/window_module.h>
#include <extra2d/services/camera_service.h> #include <extra2d/services/camera_service.h>
#include <extra2d/services/event_service.h> #include <extra2d/services/event_service.h>
#include <extra2d/services/scene_service.h> #include <extra2d/services/scene_service.h>
#include <extra2d/services/timer_service.h> #include <extra2d/services/timer_service.h>
#include <extra2d/utils/logger.h>
#include <algorithm>
#include <chrono> #include <chrono>
#include <thread> #include <thread>
@ -38,6 +36,23 @@ Application &Application::get() {
return instance; return instance;
} }
void Application::use(Module &m) {
for (auto *existing : modules_) {
if (existing == &m) {
return;
}
}
modules_.push_back(&m);
}
void Application::use(std::initializer_list<Module *> modules) {
for (auto *m : modules) {
if (m) {
use(*m);
}
}
}
bool Application::init() { bool Application::init() {
AppConfig cfg; AppConfig cfg;
return init(cfg); return init(cfg);
@ -48,140 +63,71 @@ bool Application::init(const AppConfig &config) {
return true; return true;
} }
register_config_module(); if (!initCoreModules()) {
register_platform_module();
register_window_module();
register_input_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);
}
}
return initModules();
}
bool Application::init(const std::string &configPath) {
if (initialized_) {
return true;
}
register_config_module();
register_platform_module();
register_window_module();
register_input_module();
register_render_module();
auto *configInit =
ModuleRegistry::instance().getInitializer(get_config_module_id());
if (configInit) {
auto *cfgInit = dynamic_cast<ConfigModuleInitializer *>(configInit);
if (cfgInit) {
cfgInit->setConfigPath(configPath);
}
}
return initModules();
}
void Application::registerCoreServices() {
auto &locator = ServiceLocator::instance();
if (!locator.hasService<ISceneService>()) {
locator.registerService<ISceneService>(makeShared<SceneService>());
}
if (!locator.hasService<ITimerService>()) {
locator.registerService<ITimerService>(makeShared<TimerService>());
}
if (!locator.hasService<ICameraService>()) {
auto cameraService = makeShared<CameraService>();
if (window_) {
cameraService->setViewport(0, static_cast<float>(window_->width()),
static_cast<float>(window_->height()), 0);
ViewportConfig vpConfig;
vpConfig.logicWidth = static_cast<float>(window_->width());
vpConfig.logicHeight = static_cast<float>(window_->height());
vpConfig.mode = ViewportMode::AspectRatio;
cameraService->setViewportConfig(vpConfig);
cameraService->updateViewport(window_->width(), window_->height());
}
locator.registerService<ICameraService>(cameraService);
}
}
bool Application::initModules() {
auto &locator = ServiceLocator::instance();
if (!locator.hasService<IEventService>()) {
locator.registerService<IEventService>(makeShared<EventService>());
}
auto initOrder = ModuleRegistry::instance().getInitializationOrder();
for (ModuleId moduleId : initOrder) {
auto *initializer = ModuleRegistry::instance().getInitializer(moduleId);
if (!initializer) {
continue;
}
auto *moduleConfig = ModuleRegistry::instance().getModuleConfig(moduleId);
if (!moduleConfig) {
continue;
}
auto info = moduleConfig->getModuleInfo();
if (!info.enabled) {
continue;
}
if (info.name == "Render") {
continue;
}
if (!initializer->initialize(moduleConfig)) {
return false;
}
}
auto *windowInit =
ModuleRegistry::instance().getInitializer(get_window_module_id());
if (!windowInit || !windowInit->isInitialized()) {
return false; return false;
} }
auto *windowModule = dynamic_cast<WindowModuleInitializer *>(windowInit); ConfigModule *configModule = nullptr;
if (!windowModule) { for (auto *m : modules_) {
if (auto *cm = dynamic_cast<ConfigModule *>(m)) {
configModule = cm;
break;
}
}
if (configModule) {
configModule->setAppConfig(config);
}
std::sort(modules_.begin(), modules_.end(), modulePriorityCompare);
WindowModule *windowModule = nullptr;
InputModule *inputModule = nullptr;
RenderModule *renderModule = nullptr;
for (auto *m : modules_) {
if (auto *wm = dynamic_cast<WindowModule *>(m)) {
windowModule = wm;
}
if (auto *im = dynamic_cast<InputModule *>(m)) {
inputModule = im;
}
if (auto *rm = dynamic_cast<RenderModule *>(m)) {
renderModule = rm;
}
}
for (auto *m : modules_) {
if (m == inputModule || m == renderModule) {
continue;
}
if (!m->isInitialized()) {
m->setupModule();
}
}
if (!windowModule || !windowModule->isInitialized()) {
E2D_LOG_ERROR("Window module not initialized");
return false; return false;
} }
window_ = windowModule->getWindow(); window_ = windowModule->getWindow();
if (!window_) { if (!window_) {
E2D_LOG_ERROR("Window not created");
return false; return false;
} }
auto *renderInit = registerCoreServices();
ModuleRegistry::instance().getInitializer(get_render_module_id());
if (renderInit) {
auto *renderModule = dynamic_cast<RenderModuleInitializer *>(renderInit);
if (renderModule) {
renderModule->setWindow(window_);
auto *renderConfig = if (inputModule) {
ModuleRegistry::instance().getModuleConfig(get_render_module_id()); inputModule->setWindow(window_);
if (renderConfig && !renderInit->initialize(renderConfig)) { inputModule->setupModule();
return false;
}
}
} }
registerCoreServices(); if (renderModule) {
renderModule->setWindow(window_);
renderModule->setupModule();
}
if (!ServiceLocator::instance().initializeAll()) { if (!ServiceLocator::instance().initializeAll()) {
return false; return false;
@ -211,6 +157,204 @@ bool Application::initModules() {
return true; return true;
} }
bool Application::init(const std::string &configPath) {
if (initialized_) {
return true;
}
if (!initCoreModules()) {
return false;
}
ConfigModule *configModule = nullptr;
for (auto *m : modules_) {
if (auto *cm = dynamic_cast<ConfigModule *>(m)) {
configModule = cm;
break;
}
}
if (configModule) {
configModule->setConfigPath(configPath);
}
std::sort(modules_.begin(), modules_.end(), modulePriorityCompare);
WindowModule *windowModule = nullptr;
InputModule *inputModule = nullptr;
RenderModule *renderModule = nullptr;
for (auto *m : modules_) {
if (auto *wm = dynamic_cast<WindowModule *>(m)) {
windowModule = wm;
}
if (auto *im = dynamic_cast<InputModule *>(m)) {
inputModule = im;
}
if (auto *rm = dynamic_cast<RenderModule *>(m)) {
renderModule = rm;
}
}
for (auto *m : modules_) {
if (m == inputModule || m == renderModule) {
continue;
}
if (!m->isInitialized()) {
m->setupModule();
}
}
if (!windowModule || !windowModule->isInitialized()) {
E2D_LOG_ERROR("Window module not initialized");
return false;
}
window_ = windowModule->getWindow();
if (!window_) {
E2D_LOG_ERROR("Window not created");
return false;
}
registerCoreServices();
if (inputModule) {
inputModule->setWindow(window_);
inputModule->setupModule();
}
if (renderModule) {
renderModule->setWindow(window_);
renderModule->setupModule();
}
if (!ServiceLocator::instance().initializeAll()) {
return false;
}
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
if (cameraService && window_) {
window_->onResize([cameraService](int width, int height) {
cameraService->updateViewport(width, height);
cameraService->applyViewportAdapter();
auto sceneService =
ServiceLocator::instance().getService<ISceneService>();
if (sceneService) {
auto currentScene = sceneService->getCurrentScene();
if (currentScene) {
currentScene->setViewportSize(static_cast<float>(width),
static_cast<float>(height));
}
}
});
}
initialized_ = true;
running_ = true;
return true;
}
bool Application::initCoreModules() {
bool hasConfig = false;
bool hasPlatform = false;
bool hasWindow = false;
bool hasInput = false;
bool hasRender = false;
for (auto *m : modules_) {
if (dynamic_cast<ConfigModule *>(m))
hasConfig = true;
if (dynamic_cast<PlatformModule *>(m))
hasPlatform = true;
if (dynamic_cast<WindowModule *>(m))
hasWindow = true;
if (dynamic_cast<InputModule *>(m))
hasInput = true;
if (dynamic_cast<RenderModule *>(m))
hasRender = true;
}
if (!hasConfig) {
static ConfigModule defaultConfigModule;
use(defaultConfigModule);
}
if (!hasPlatform) {
static PlatformModule defaultPlatformModule;
use(defaultPlatformModule);
}
if (!hasWindow) {
static WindowModule defaultWindowModule;
use(defaultWindowModule);
}
if (!hasInput) {
static InputModule defaultInputModule;
use(defaultInputModule);
}
if (!hasRender) {
static RenderModule defaultRenderModule;
use(defaultRenderModule);
}
return true;
}
void Application::setupAllModules() {
std::sort(modules_.begin(), modules_.end(), modulePriorityCompare);
for (auto *m : modules_) {
if (!m->isInitialized()) {
m->setupModule();
}
}
}
void Application::destroyAllModules() {
for (auto it = modules_.rbegin(); it != modules_.rend(); ++it) {
Module *m = *it;
if (m->isInitialized()) {
m->destroyModule();
}
}
modules_.clear();
}
void Application::registerCoreServices() {
auto &locator = ServiceLocator::instance();
if (!locator.hasService<IEventService>()) {
locator.registerService<IEventService>(makeShared<EventService>());
}
if (!locator.hasService<ISceneService>()) {
locator.registerService<ISceneService>(makeShared<SceneService>());
}
if (!locator.hasService<ITimerService>()) {
locator.registerService<ITimerService>(makeShared<TimerService>());
}
if (!locator.hasService<ICameraService>()) {
auto cameraService = makeShared<CameraService>();
if (window_) {
cameraService->setViewport(0, static_cast<float>(window_->width()),
static_cast<float>(window_->height()), 0);
ViewportConfig vpConfig;
vpConfig.logicWidth = static_cast<float>(window_->width());
vpConfig.logicHeight = static_cast<float>(window_->height());
vpConfig.mode = ViewportMode::AspectRatio;
cameraService->setViewportConfig(vpConfig);
cameraService->updateViewport(window_->width(), window_->height());
}
locator.registerService<ICameraService>(cameraService);
}
}
void Application::shutdown() { void Application::shutdown() {
if (!initialized_) if (!initialized_)
return; return;
@ -221,15 +365,7 @@ void Application::shutdown() {
window_ = nullptr; window_ = nullptr;
auto initOrder = ModuleRegistry::instance().getInitializationOrder(); destroyAllModules();
for (auto it = initOrder.rbegin(); it != initOrder.rend(); ++it) {
ModuleId moduleId = *it;
auto *initializer = ModuleRegistry::instance().getInitializer(moduleId);
if (initializer && initializer->isInitialized()) {
initializer->shutdown();
}
}
initialized_ = false; initialized_ = false;
running_ = false; running_ = false;
@ -301,37 +437,34 @@ void Application::mainLoop() {
render(); render();
const auto &appConfig = ConfigManager::instance().appConfig(); RenderModule *renderModule = nullptr;
for (auto *m : modules_) {
auto *renderConfig = if (auto *rm = dynamic_cast<RenderModule *>(m)) {
ModuleRegistry::instance().getModuleConfig(get_render_module_id()); renderModule = rm;
auto *renderModuleConfig = break;
dynamic_cast<const RenderModuleConfig *>(renderConfig);
if (renderModuleConfig && !renderModuleConfig->vsync &&
renderModuleConfig->targetFPS > 0) {
double frameEndTime = getTimeSeconds();
double frameTime = frameEndTime - currentTime;
double target = 1.0 / static_cast<double>(renderModuleConfig->targetFPS);
if (frameTime < target) {
auto sleepSeconds = target - frameTime;
std::this_thread::sleep_for(std::chrono::duration<double>(sleepSeconds));
} }
} }
(void)renderModule;
ConfigManager::instance().update(deltaTime_); ConfigManager::instance().update(deltaTime_);
} }
void Application::update() { ServiceLocator::instance().updateAll(deltaTime_); } void Application::update() {
ServiceLocator::instance().updateAll(deltaTime_);
auto ctx = UpdateContext(modules_, deltaTime_);
if (!modules_.empty()) {
ctx.next();
}
}
void Application::render() { void Application::render() {
auto *renderInit =
ModuleRegistry::instance().getInitializer(get_render_module_id());
RenderBackend *renderer = nullptr; RenderBackend *renderer = nullptr;
if (renderInit) { for (auto *m : modules_) {
auto *renderModule = dynamic_cast<RenderModuleInitializer *>(renderInit); if (auto *rm = dynamic_cast<RenderModule *>(m)) {
if (renderModule) { renderer = rm->getRenderer();
renderer = renderModule->getRenderer(); break;
} }
} }
@ -351,23 +484,41 @@ void Application::render() {
renderer->setViewport(0, 0, window_->width(), window_->height()); renderer->setViewport(0, 0, window_->width(), window_->height());
} }
{
auto ctx = RenderContext(modules_, RenderContext::Phase::Before);
if (!modules_.empty()) {
ctx.next();
}
}
{
auto ctx = RenderContext(modules_, RenderContext::Phase::On);
if (!modules_.empty()) {
ctx.next();
}
}
auto sceneService = ServiceLocator::instance().getService<ISceneService>(); auto sceneService = ServiceLocator::instance().getService<ISceneService>();
if (sceneService) { if (sceneService) {
sceneService->render(*renderer); sceneService->render(*renderer);
} }
window_->swap(); {
auto ctx = RenderContext(modules_, RenderContext::Phase::After);
if (!modules_.empty()) {
ctx.next();
}
}
} }
IInput &Application::input() { return *window_->input(); } IInput &Application::input() { return *window_->input(); }
RenderBackend &Application::renderer() { RenderBackend &Application::renderer() {
auto *renderInit = for (auto *m : modules_) {
ModuleRegistry::instance().getInitializer(get_render_module_id()); if (auto *rm = dynamic_cast<RenderModule *>(m)) {
if (renderInit) { if (rm->getRenderer()) {
auto *renderModule = dynamic_cast<RenderModuleInitializer *>(renderInit); return *rm->getRenderer();
if (renderModule && renderModule->getRenderer()) { }
return *renderModule->getRenderer();
} }
} }
static RenderBackend *dummy = nullptr; static RenderBackend *dummy = nullptr;

View File

@ -1,13 +1,11 @@
#include <extra2d/config/config_loader.h> #include <extra2d/config/config_loader.h>
#include <extra2d/config/config_manager.h> #include <extra2d/config/config_manager.h>
#include <extra2d/config/module_registry.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cctype> #include <cctype>
#include <fstream>
#include <map> #include <map>
#include <sstream>
namespace extra2d { namespace extra2d {
@ -16,16 +14,18 @@ namespace extra2d {
* @param str * @param str
* @return * @return
*/ */
static std::string trim(const std::string& str) { static std::string trim(const std::string &str) {
size_t start = 0; size_t start = 0;
while (start < str.length() && std::isspace(static_cast<unsigned char>(str[start]))) { while (start < str.length() &&
++start; std::isspace(static_cast<unsigned char>(str[start]))) {
} ++start;
size_t end = str.length(); }
while (end > start && std::isspace(static_cast<unsigned char>(str[end - 1]))) { size_t end = str.length();
--end; while (end > start &&
} std::isspace(static_cast<unsigned char>(str[end - 1]))) {
return str.substr(start, end - start); --end;
}
return str.substr(start, end - start);
} }
/** /**
@ -33,12 +33,12 @@ static std::string trim(const std::string& str) {
* @param str * @param str
* @return * @return
*/ */
static std::string toLower(const std::string& str) { static std::string toLower(const std::string &str) {
std::string result = str; std::string result = str;
for (char& c : result) { for (char &c : result) {
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c))); c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
} }
return result; return result;
} }
/** /**
@ -52,47 +52,49 @@ using IniData = std::map<std::string, std::map<std::string, std::string>>;
* @param data INI * @param data INI
* @return * @return
*/ */
static ConfigLoadResult parseIniContent(const std::string& content, IniData& data) { static ConfigLoadResult parseIniContent(const std::string &content,
std::istringstream stream(content); IniData &data) {
std::string line; std::istringstream stream(content);
std::string currentSection; std::string line;
int lineNumber = 0; std::string currentSection;
int lineNumber = 0;
while (std::getline(stream, line)) {
++lineNumber; while (std::getline(stream, line)) {
line = trim(line); ++lineNumber;
line = trim(line);
if (line.empty() || line[0] == ';' || line[0] == '#') {
continue; if (line.empty() || line[0] == ';' || line[0] == '#') {
} continue;
if (line[0] == '[') {
size_t endBracket = line.find(']');
if (endBracket == std::string::npos) {
return ConfigLoadResult::error("INI 解析错误: 缺少右括号", lineNumber);
}
currentSection = trim(line.substr(1, endBracket - 1));
if (data.find(currentSection) == data.end()) {
data[currentSection] = std::map<std::string, std::string>();
}
} else {
size_t equalPos = line.find('=');
if (equalPos == std::string::npos) {
continue;
}
std::string key = trim(line.substr(0, equalPos));
std::string value = trim(line.substr(equalPos + 1));
if (currentSection.empty()) {
return ConfigLoadResult::error("INI 解析错误: 键值对不在任何节中", lineNumber);
}
data[currentSection][key] = value;
}
} }
return ConfigLoadResult::ok(); if (line[0] == '[') {
size_t endBracket = line.find(']');
if (endBracket == std::string::npos) {
return ConfigLoadResult::error("INI 解析错误: 缺少右括号", lineNumber);
}
currentSection = trim(line.substr(1, endBracket - 1));
if (data.find(currentSection) == data.end()) {
data[currentSection] = std::map<std::string, std::string>();
}
} else {
size_t equalPos = line.find('=');
if (equalPos == std::string::npos) {
continue;
}
std::string key = trim(line.substr(0, equalPos));
std::string value = trim(line.substr(equalPos + 1));
if (currentSection.empty()) {
return ConfigLoadResult::error("INI 解析错误: 键值对不在任何节中",
lineNumber);
}
data[currentSection][key] = value;
}
}
return ConfigLoadResult::ok();
} }
/** /**
@ -103,17 +105,18 @@ static ConfigLoadResult parseIniContent(const std::string& content, IniData& dat
* @param defaultValue * @param defaultValue
* @return * @return
*/ */
static std::string getIniValue(const IniData& data, const std::string& section, static std::string getIniValue(const IniData &data, const std::string &section,
const std::string& key, const std::string& defaultValue = "") { const std::string &key,
auto sectionIt = data.find(section); const std::string &defaultValue = "") {
if (sectionIt == data.end()) { auto sectionIt = data.find(section);
return defaultValue; if (sectionIt == data.end()) {
} return defaultValue;
auto keyIt = sectionIt->second.find(key); }
if (keyIt == sectionIt->second.end()) { auto keyIt = sectionIt->second.find(key);
return defaultValue; if (keyIt == sectionIt->second.end()) {
} return defaultValue;
return keyIt->second; }
return keyIt->second;
} }
/** /**
@ -123,219 +126,207 @@ static std::string getIniValue(const IniData& data, const std::string& section,
* @param key * @param key
* @return * @return
*/ */
static bool hasIniValue(const IniData& data, const std::string& section, const std::string& key) { static bool hasIniValue(const IniData &data, const std::string &section,
auto sectionIt = data.find(section); const std::string &key) {
if (sectionIt == data.end()) { auto sectionIt = data.find(section);
return false; if (sectionIt == data.end()) {
}
return sectionIt->second.find(key) != sectionIt->second.end();
}
ConfigLoadResult IniConfigLoader::load(const std::string& filepath, AppConfig& config) {
E2D_LOG_INFO("正在从 INI 文件加载应用配置: {}", filepath);
std::ifstream file(filepath);
if (!file.is_open()) {
E2D_LOG_ERROR("无法打开配置文件: {}", filepath);
return ConfigLoadResult::error("无法打开配置文件: " + filepath);
}
std::stringstream buffer;
buffer << file.rdbuf();
std::string content = buffer.str();
file.close();
return loadFromString(content, config);
}
ConfigSaveResult IniConfigLoader::save(const std::string& filepath, const AppConfig& config) {
E2D_LOG_INFO("正在保存应用配置到 INI 文件: {}", filepath);
std::string content = saveToString(config);
std::ofstream file(filepath);
if (!file.is_open()) {
E2D_LOG_ERROR("无法创建配置文件: {}", filepath);
return ConfigSaveResult::error("无法创建配置文件: " + filepath);
}
file << content;
file.close();
E2D_LOG_INFO("配置已成功保存到: {}", filepath);
return ConfigSaveResult::ok();
}
ConfigLoadResult IniConfigLoader::loadFromString(const std::string& content, AppConfig& config) {
IniData data;
auto result = parseIniContent(content, data);
if (result.hasError()) {
return result;
}
if (hasIniValue(data, "app", "name")) {
config.appName = getIniValue(data, "app", "name");
}
if (hasIniValue(data, "app", "version")) {
config.appVersion = getIniValue(data, "app", "version");
}
if (hasIniValue(data, "app", "organization")) {
config.organization = getIniValue(data, "app", "organization");
}
if (hasIniValue(data, "app", "configFile")) {
config.configFile = getIniValue(data, "app", "configFile");
}
if (hasIniValue(data, "app", "targetPlatform")) {
int value;
auto res = parseInt(getIniValue(data, "app", "targetPlatform"), value, "app.targetPlatform");
if (res.isOk()) {
config.targetPlatform = static_cast<PlatformType>(value);
}
}
E2D_LOG_INFO("INI 应用配置加载成功");
return ConfigLoadResult::ok();
}
std::string IniConfigLoader::saveToString(const AppConfig& config) {
std::ostringstream oss;
oss << "[app]\n";
oss << "name=" << config.appName << "\n";
oss << "version=" << config.appVersion << "\n";
oss << "organization=" << config.organization << "\n";
oss << "configFile=" << config.configFile << "\n";
oss << "targetPlatform=" << static_cast<int>(config.targetPlatform) << "\n";
return oss.str();
}
ConfigLoadResult IniConfigLoader::loadWithModules(const std::string& filepath) {
E2D_LOG_INFO("正在从 INI 文件加载完整配置(含模块): {}", filepath);
std::ifstream file(filepath);
if (!file.is_open()) {
E2D_LOG_ERROR("无法打开配置文件: {}", filepath);
return ConfigLoadResult::error("无法打开配置文件: " + filepath);
}
std::stringstream buffer;
buffer << file.rdbuf();
std::string content = buffer.str();
file.close();
IniData data;
auto result = parseIniContent(content, data);
if (result.hasError()) {
return result;
}
auto& registry = ModuleRegistry::instance();
auto moduleIds = registry.getAllModules();
for (ModuleId moduleId : moduleIds) {
IModuleConfig* moduleConfig = registry.getModuleConfig(moduleId);
if (!moduleConfig) continue;
std::string sectionName = moduleConfig->getConfigSectionName();
if (sectionName.empty()) continue;
if (data.find(sectionName) != data.end()) {
E2D_LOG_DEBUG("加载模块 {} 的 INI 配置", moduleConfig->getModuleInfo().name);
}
}
E2D_LOG_INFO("完整配置加载成功");
return ConfigLoadResult::ok();
}
ConfigSaveResult IniConfigLoader::saveWithModules(const std::string& filepath) {
E2D_LOG_INFO("正在保存完整配置(含模块)到 INI 文件: {}", filepath);
std::ostringstream oss;
oss << saveToString(ConfigManager::instance().appConfig());
auto& registry = ModuleRegistry::instance();
auto moduleIds = registry.getAllModules();
for (ModuleId moduleId : moduleIds) {
IModuleConfig* moduleConfig = registry.getModuleConfig(moduleId);
if (!moduleConfig) continue;
std::string sectionName = moduleConfig->getConfigSectionName();
if (sectionName.empty()) continue;
oss << "\n[" << sectionName << "]\n";
}
std::ofstream file(filepath);
if (!file.is_open()) {
E2D_LOG_ERROR("无法创建配置文件: {}", filepath);
return ConfigSaveResult::error("无法创建配置文件: " + filepath);
}
file << oss.str();
file.close();
E2D_LOG_INFO("完整配置已成功保存到: {}", filepath);
return ConfigSaveResult::ok();
}
bool IniConfigLoader::supportsFile(const std::string& filepath) const {
if (filepath.length() >= 4) {
std::string ext = filepath.substr(filepath.length() - 4);
for (char& c : ext) c = static_cast<char>(std::tolower(c));
return ext == ".ini";
}
return false; return false;
}
return sectionIt->second.find(key) != sectionIt->second.end();
}
ConfigLoadResult IniConfigLoader::load(const std::string &filepath,
AppConfig &config) {
E2D_LOG_INFO("正在从 INI 文件加载应用配置: {}", filepath);
std::ifstream file(filepath);
if (!file.is_open()) {
E2D_LOG_ERROR("无法打开配置文件: {}", filepath);
return ConfigLoadResult::error("无法打开配置文件: " + filepath);
}
std::stringstream buffer;
buffer << file.rdbuf();
std::string content = buffer.str();
file.close();
return loadFromString(content, config);
}
ConfigSaveResult IniConfigLoader::save(const std::string &filepath,
const AppConfig &config) {
E2D_LOG_INFO("正在保存应用配置到 INI 文件: {}", filepath);
std::string content = saveToString(config);
std::ofstream file(filepath);
if (!file.is_open()) {
E2D_LOG_ERROR("无法创建配置文件: {}", filepath);
return ConfigSaveResult::error("无法创建配置文件: " + filepath);
}
file << content;
file.close();
E2D_LOG_INFO("配置已成功保存到: {}", filepath);
return ConfigSaveResult::ok();
}
ConfigLoadResult IniConfigLoader::loadFromString(const std::string &content,
AppConfig &config) {
IniData data;
auto result = parseIniContent(content, data);
if (result.hasError()) {
return result;
}
if (hasIniValue(data, "app", "name")) {
config.appName = getIniValue(data, "app", "name");
}
if (hasIniValue(data, "app", "version")) {
config.appVersion = getIniValue(data, "app", "version");
}
if (hasIniValue(data, "app", "organization")) {
config.organization = getIniValue(data, "app", "organization");
}
if (hasIniValue(data, "app", "configFile")) {
config.configFile = getIniValue(data, "app", "configFile");
}
if (hasIniValue(data, "app", "targetPlatform")) {
int value;
auto res = parseInt(getIniValue(data, "app", "targetPlatform"), value,
"app.targetPlatform");
if (res.isOk()) {
config.targetPlatform = static_cast<PlatformType>(value);
}
}
E2D_LOG_INFO("INI 应用配置加载成功");
return ConfigLoadResult::ok();
}
std::string IniConfigLoader::saveToString(const AppConfig &config) {
std::ostringstream oss;
oss << "[app]\n";
oss << "name=" << config.appName << "\n";
oss << "version=" << config.appVersion << "\n";
oss << "organization=" << config.organization << "\n";
oss << "configFile=" << config.configFile << "\n";
oss << "targetPlatform=" << static_cast<int>(config.targetPlatform) << "\n";
return oss.str();
}
ConfigLoadResult IniConfigLoader::loadWithModules(const std::string &filepath) {
E2D_LOG_INFO("正在从 INI 文件加载完整配置: {}", filepath);
std::ifstream file(filepath);
if (!file.is_open()) {
E2D_LOG_ERROR("无法打开配置文件: {}", filepath);
return ConfigLoadResult::error("无法打开配置文件: " + filepath);
}
std::stringstream buffer;
buffer << file.rdbuf();
std::string content = buffer.str();
file.close();
IniData data;
auto result = parseIniContent(content, data);
if (result.hasError()) {
return result;
}
E2D_LOG_INFO("完整配置加载成功");
return ConfigLoadResult::ok();
}
ConfigSaveResult IniConfigLoader::saveWithModules(const std::string &filepath) {
E2D_LOG_INFO("正在保存完整配置到 INI 文件: {}", filepath);
std::ostringstream oss;
oss << saveToString(ConfigManager::instance().appConfig());
std::ofstream file(filepath);
if (!file.is_open()) {
E2D_LOG_ERROR("无法创建配置文件: {}", filepath);
return ConfigSaveResult::error("无法创建配置文件: " + filepath);
}
file << oss.str();
file.close();
E2D_LOG_INFO("完整配置已成功保存到: {}", filepath);
return ConfigSaveResult::ok();
}
bool IniConfigLoader::supportsFile(const std::string &filepath) const {
if (filepath.length() >= 4) {
std::string ext = filepath.substr(filepath.length() - 4);
for (char &c : ext)
c = static_cast<char>(std::tolower(c));
return ext == ".ini";
}
return false;
} }
UniquePtr<ConfigLoader> IniConfigLoader::clone() const { UniquePtr<ConfigLoader> IniConfigLoader::clone() const {
return makeUnique<IniConfigLoader>(); return makeUnique<IniConfigLoader>();
} }
std::string IniConfigLoader::sectionKey(const std::string& section, const std::string& key) const { std::string IniConfigLoader::sectionKey(const std::string &section,
return section + "." + key; const std::string &key) const {
return section + "." + key;
} }
ConfigLoadResult IniConfigLoader::parseInt(const std::string& value, int& result, const std::string& fieldName) { ConfigLoadResult IniConfigLoader::parseInt(const std::string &value,
try { int &result,
size_t pos; const std::string &fieldName) {
result = std::stoi(value, &pos); try {
if (pos != value.length()) { size_t pos;
return ConfigLoadResult::error("无法解析整数值: " + value, -1, fieldName); result = std::stoi(value, &pos);
} if (pos != value.length()) {
return ConfigLoadResult::ok(); return ConfigLoadResult::error("无法解析整数值: " + value, -1, fieldName);
} catch (const std::exception& e) {
return ConfigLoadResult::error(std::string("解析整数失败: ") + e.what(), -1, fieldName);
} }
return ConfigLoadResult::ok();
} catch (const std::exception &e) {
return ConfigLoadResult::error(std::string("解析整数失败: ") + e.what(), -1,
fieldName);
}
} }
ConfigLoadResult IniConfigLoader::parseFloat(const std::string& value, float& result, const std::string& fieldName) { ConfigLoadResult IniConfigLoader::parseFloat(const std::string &value,
try { float &result,
size_t pos; const std::string &fieldName) {
result = std::stof(value, &pos); try {
if (pos != value.length()) { size_t pos;
return ConfigLoadResult::error("无法解析浮点数值: " + value, -1, fieldName); result = std::stof(value, &pos);
} if (pos != value.length()) {
return ConfigLoadResult::ok(); return ConfigLoadResult::error("无法解析浮点数值: " + value, -1,
} catch (const std::exception& e) { fieldName);
return ConfigLoadResult::error(std::string("解析浮点数失败: ") + e.what(), -1, fieldName);
} }
return ConfigLoadResult::ok();
} catch (const std::exception &e) {
return ConfigLoadResult::error(std::string("解析浮点数失败: ") + e.what(),
-1, fieldName);
}
} }
ConfigLoadResult IniConfigLoader::parseBool(const std::string& value, bool& result, const std::string& fieldName) { ConfigLoadResult IniConfigLoader::parseBool(const std::string &value,
std::string lower = toLower(value); bool &result,
if (lower == "true" || lower == "1" || lower == "yes" || lower == "on") { const std::string &fieldName) {
result = true; std::string lower = toLower(value);
return ConfigLoadResult::ok(); if (lower == "true" || lower == "1" || lower == "yes" || lower == "on") {
} result = true;
if (lower == "false" || lower == "0" || lower == "no" || lower == "off") { return ConfigLoadResult::ok();
result = false; }
return ConfigLoadResult::ok(); if (lower == "false" || lower == "0" || lower == "no" || lower == "off") {
} result = false;
return ConfigLoadResult::error("无法解析布尔值: " + value, -1, fieldName); return ConfigLoadResult::ok();
}
return ConfigLoadResult::error("无法解析布尔值: " + value, -1, fieldName);
} }
} } // namespace extra2d

View File

@ -1,5 +1,4 @@
#include <extra2d/config/config_loader.h> #include <extra2d/config/config_loader.h>
#include <extra2d/config/module_registry.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@ -93,7 +92,7 @@ std::string JsonConfigLoader::saveToString(const AppConfig& config) {
} }
ConfigLoadResult JsonConfigLoader::loadWithModules(const std::string& filepath) { ConfigLoadResult JsonConfigLoader::loadWithModules(const std::string& filepath) {
E2D_LOG_INFO("正在从 JSON 文件加载完整配置(含模块): {}", filepath); E2D_LOG_INFO("正在从 JSON 文件加载完整配置: {}", filepath);
std::ifstream file(filepath); std::ifstream file(filepath);
if (!file.is_open()) { if (!file.is_open()) {
@ -115,50 +114,15 @@ ConfigLoadResult JsonConfigLoader::loadWithModules(const std::string& filepath)
static_cast<int>(e.byte)); static_cast<int>(e.byte));
} }
auto& registry = ModuleRegistry::instance();
auto moduleIds = registry.getAllModules();
for (ModuleId moduleId : moduleIds) {
IModuleConfig* moduleConfig = registry.getModuleConfig(moduleId);
if (!moduleConfig) continue;
std::string sectionName = moduleConfig->getConfigSectionName();
if (sectionName.empty()) continue;
if (root.contains(sectionName)) {
if (!moduleConfig->loadFromJson(&root[sectionName])) {
E2D_LOG_WARN("模块 {} 配置加载失败", moduleConfig->getModuleInfo().name);
} else {
E2D_LOG_DEBUG("模块 {} 配置加载成功", moduleConfig->getModuleInfo().name);
}
}
}
E2D_LOG_INFO("完整配置加载成功"); E2D_LOG_INFO("完整配置加载成功");
return ConfigLoadResult::ok(); return ConfigLoadResult::ok();
} }
ConfigSaveResult JsonConfigLoader::saveWithModules(const std::string& filepath) { ConfigSaveResult JsonConfigLoader::saveWithModules(const std::string& filepath) {
E2D_LOG_INFO("正在保存完整配置(含模块)到 JSON 文件: {}", filepath); E2D_LOG_INFO("正在保存完整配置到 JSON 文件: {}", filepath);
json root; json root;
auto& registry = ModuleRegistry::instance();
auto moduleIds = registry.getAllModules();
for (ModuleId moduleId : moduleIds) {
IModuleConfig* moduleConfig = registry.getModuleConfig(moduleId);
if (!moduleConfig) continue;
std::string sectionName = moduleConfig->getConfigSectionName();
if (sectionName.empty()) continue;
json sectionJson;
if (moduleConfig->saveToJson(&sectionJson)) {
root[sectionName] = sectionJson;
}
}
std::ofstream file(filepath); std::ofstream file(filepath);
if (!file.is_open()) { if (!file.is_open()) {
E2D_LOG_ERROR("无法创建配置文件: {}", filepath); E2D_LOG_ERROR("无法创建配置文件: {}", filepath);

View File

@ -1,114 +0,0 @@
#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,211 +0,0 @@
#include <extra2d/config/module_registry.h>
#include <algorithm>
#include <cstdio>
namespace extra2d {
/**
* @brief
* 使线
* @return
*/
ModuleRegistry& ModuleRegistry::instance() {
static ModuleRegistry instance;
return instance;
}
/**
* @brief
*
* @param config
* @param initializerFactory
* @return
*/
ModuleId ModuleRegistry::registerModule(
UniquePtr<IModuleConfig> config,
ModuleInitializerFactory initializerFactory
) {
if (!config) {
std::fprintf(stderr, "[ERROR] Cannot register null module config\n");
return INVALID_MODULE_ID;
}
std::lock_guard<std::mutex> lock(mutex_);
ModuleInfo info = config->getModuleInfo();
if (nameToId_.find(info.name) != nameToId_.end()) {
std::fprintf(stderr, "[ERROR] Module '%s' already registered\n", info.name.c_str());
return INVALID_MODULE_ID;
}
ModuleId id = generateId();
ModuleEntry entry;
entry.id = id;
entry.config = std::move(config);
entry.initializerFactory = std::move(initializerFactory);
entry.initialized = false;
modules_[id] = std::move(entry);
nameToId_[info.name] = id;
return id;
}
/**
* @brief
*
* @param id
* @return true
*/
bool ModuleRegistry::unregisterModule(ModuleId id) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = modules_.find(id);
if (it == modules_.end()) {
return false;
}
ModuleInfo info = it->second.config->getModuleInfo();
nameToId_.erase(info.name);
modules_.erase(it);
return true;
}
/**
* @brief
* @param id
* @return nullptr
*/
IModuleConfig* ModuleRegistry::getModuleConfig(ModuleId id) const {
std::lock_guard<std::mutex> lock(mutex_);
auto it = modules_.find(id);
if (it != modules_.end()) {
return it->second.config.get();
}
return nullptr;
}
/**
* @brief
* @param name
* @return nullptr
*/
IModuleConfig* ModuleRegistry::getModuleConfigByName(const std::string& name) const {
std::lock_guard<std::mutex> lock(mutex_);
auto nameIt = nameToId_.find(name);
if (nameIt == nameToId_.end()) {
return nullptr;
}
auto moduleIt = modules_.find(nameIt->second);
if (moduleIt != modules_.end()) {
return moduleIt->second.config.get();
}
return nullptr;
}
/**
* @brief
* @param id
* @return nullptr
*/
IModuleInitializer* ModuleRegistry::getInitializer(ModuleId id) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = modules_.find(id);
if (it == modules_.end() || !it->second.initializerFactory) {
return nullptr;
}
if (!it->second.initializer) {
it->second.initializer = it->second.initializerFactory();
}
return it->second.initializer.get();
}
/**
* @brief
* @return
*/
std::vector<ModuleId> ModuleRegistry::getAllModules() const {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<ModuleId> result;
result.reserve(modules_.size());
for (const auto& pair : modules_) {
result.push_back(pair.first);
}
return result;
}
/**
* @brief
*
* @return
*/
std::vector<ModuleId> ModuleRegistry::getInitializationOrder() const {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<std::pair<ModuleId, int>> modulePriorities;
modulePriorities.reserve(modules_.size());
for (const auto& pair : modules_) {
ModuleInfo info = pair.second.config->getModuleInfo();
if (info.enabled) {
modulePriorities.emplace_back(pair.first, static_cast<int>(info.priority));
}
}
std::sort(modulePriorities.begin(), modulePriorities.end(),
[](const auto& a, const auto& b) {
return a.second < b.second;
});
std::vector<ModuleId> result;
result.reserve(modulePriorities.size());
for (const auto& pair : modulePriorities) {
result.push_back(pair.first);
}
return result;
}
/**
* @brief
* @param id
* @return true
*/
bool ModuleRegistry::hasModule(ModuleId id) const {
std::lock_guard<std::mutex> lock(mutex_);
return modules_.find(id) != modules_.end();
}
/**
* @brief
*/
void ModuleRegistry::clear() {
std::lock_guard<std::mutex> lock(mutex_);
modules_.clear();
nameToId_.clear();
nextId_ = 1;
}
/**
* @brief
* @return
*/
ModuleId ModuleRegistry::generateId() {
return nextId_++;
}
}

View File

@ -0,0 +1,69 @@
#include <extra2d/core/module.h>
namespace extra2d {
// ---------------------------------------------------------------------------
// ModuleContext 实现
// ---------------------------------------------------------------------------
ModuleContext::ModuleContext(std::vector<Module*>& modules)
: modules_(modules)
, index_(-1) {
}
void ModuleContext::next() {
index_++;
if (index_ < static_cast<int>(modules_.size())) {
handle(modules_[index_]);
}
}
// ---------------------------------------------------------------------------
// UpdateContext 实现
// ---------------------------------------------------------------------------
UpdateContext::UpdateContext(std::vector<Module*>& modules, float deltaTime)
: ModuleContext(modules)
, deltaTime_(deltaTime) {
}
void UpdateContext::handle(Module* m) {
m->onUpdate(*this);
}
// ---------------------------------------------------------------------------
// RenderContext 实现
// ---------------------------------------------------------------------------
RenderContext::RenderContext(std::vector<Module*>& modules, Phase phase)
: ModuleContext(modules)
, phase_(phase) {
}
void RenderContext::handle(Module* m) {
switch (phase_) {
case Phase::Before:
m->beforeRender(*this);
break;
case Phase::On:
m->onRender(*this);
break;
case Phase::After:
m->afterRender(*this);
break;
}
}
// ---------------------------------------------------------------------------
// EventContext 实现
// ---------------------------------------------------------------------------
EventContext::EventContext(std::vector<Module*>& modules)
: ModuleContext(modules) {
}
void EventContext::handle(Module* m) {
m->handleEvent(*this);
}
} // namespace extra2d

View File

@ -1,214 +0,0 @@
#include <extra2d/graphics/render_module.h>
#include <extra2d/config/module_registry.h>
#include <extra2d/graphics/opengl/gl_shader.h>
#include <extra2d/graphics/shader_manager.h>
#include <extra2d/platform/iwindow.h>
#include <extra2d/utils/logger.h>
#include <nlohmann/json.hpp>
#include <algorithm>
using json = nlohmann::json;
namespace extra2d {
static ModuleId s_renderModuleId = INVALID_MODULE_ID;
ModuleId get_render_module_id() {
return s_renderModuleId;
}
bool RenderModuleConfig::validate() const {
if (targetFPS < 1 || targetFPS > 240) {
return false;
}
if (multisamples != 0 && multisamples != 2 && multisamples != 4 &&
multisamples != 8 && multisamples != 16) {
return false;
}
if (spriteBatchSize <= 0) {
return false;
}
return true;
}
void RenderModuleConfig::applyPlatformConstraints(PlatformType platform) {
switch (platform) {
case PlatformType::Switch:
if (multisamples > 4) {
multisamples = 4;
}
if (sRGBFramebuffer) {
sRGBFramebuffer = false;
}
if (targetFPS > 60) {
targetFPS = 60;
}
break;
default:
break;
}
}
void RenderModuleConfig::resetToDefaults() {
backend = BackendType::OpenGL;
vsync = true;
targetFPS = 60;
multisamples = 0;
sRGBFramebuffer = false;
spriteBatchSize = 1000;
}
bool RenderModuleConfig::loadFromJson(const void* jsonData) {
if (!jsonData) return false;
try {
const json& j = *static_cast<const json*>(jsonData);
if (j.contains("backend")) {
std::string backendStr = j["backend"].get<std::string>();
if (backendStr == "opengl") {
backend = BackendType::OpenGL;
}
}
if (j.contains("vsync")) {
vsync = j["vsync"].get<bool>();
}
if (j.contains("targetFPS")) {
targetFPS = j["targetFPS"].get<int>();
}
if (j.contains("multisamples")) {
multisamples = j["multisamples"].get<int>();
}
if (j.contains("sRGBFramebuffer")) {
sRGBFramebuffer = j["sRGBFramebuffer"].get<bool>();
}
if (j.contains("spriteBatchSize")) {
spriteBatchSize = j["spriteBatchSize"].get<int>();
}
return true;
} catch (...) {
return false;
}
}
bool RenderModuleConfig::saveToJson(void* jsonData) const {
if (!jsonData) return false;
try {
json& j = *static_cast<json*>(jsonData);
j["backend"] = "opengl";
j["vsync"] = vsync;
j["targetFPS"] = targetFPS;
j["multisamples"] = multisamples;
j["sRGBFramebuffer"] = sRGBFramebuffer;
j["spriteBatchSize"] = spriteBatchSize;
return true;
} catch (...) {
return false;
}
}
RenderModuleInitializer::RenderModuleInitializer()
: moduleId_(INVALID_MODULE_ID)
, window_(nullptr)
, initialized_(false) {
}
RenderModuleInitializer::~RenderModuleInitializer() {
if (initialized_) {
shutdown();
}
}
std::vector<ModuleId> RenderModuleInitializer::getDependencies() const {
return {};
}
bool RenderModuleInitializer::initialize(const IModuleConfig* config) {
if (initialized_) return true;
if (!config) return false;
const RenderModuleConfig* renderConfig = dynamic_cast<const RenderModuleConfig*>(config);
if (!renderConfig) return false;
if (!renderConfig->validate()) return false;
if (!window_) {
E2D_LOG_ERROR("Render module requires window to be set");
return false;
}
auto shaderFactory = std::make_shared<GLShaderFactory>();
if (!ShaderManager::getInstance().init(shaderFactory, "extra2d")) {
E2D_LOG_WARN("Failed to initialize ShaderManager with default paths");
}
if (!ShaderManager::getInstance().loadBuiltinShaders()) {
E2D_LOG_WARN("Failed to load some builtin shaders");
}
renderer_ = RenderBackend::create(renderConfig->backend);
if (!renderer_) {
E2D_LOG_ERROR("Failed to create render backend");
return false;
}
if (!renderer_->init(window_)) {
E2D_LOG_ERROR("Failed to initialize renderer");
renderer_.reset();
return false;
}
initialized_ = true;
E2D_LOG_INFO("Render module initialized");
return true;
}
void RenderModuleInitializer::shutdown() {
if (!initialized_) return;
if (renderer_) {
renderer_->shutdown();
renderer_.reset();
}
ShaderManager::getInstance().shutdown();
initialized_ = false;
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,53 @@
#include <extra2d/modules/config_module.h>
#include <extra2d/utils/logger.h>
namespace extra2d {
ConfigModule::ConfigModule()
: Module() {
}
ConfigModule::~ConfigModule() {
if (isInitialized()) {
destroyModule();
}
}
void ConfigModule::setupModule() {
if (isInitialized()) {
return;
}
if (!configPath_.empty()) {
if (!ConfigManager::instance().initialize(configPath_)) {
if (!ConfigManager::instance().initialize()) {
E2D_LOG_ERROR("Config module initialization failed");
return;
}
}
} else {
if (!ConfigManager::instance().initialize()) {
E2D_LOG_ERROR("Config module initialization failed");
return;
}
}
if (!appConfig_.appName.empty()) {
ConfigManager::instance().setAppConfig(appConfig_);
}
setInitialized(true);
E2D_LOG_INFO("Config module initialized");
}
void ConfigModule::destroyModule() {
if (!isInitialized()) {
return;
}
E2D_LOG_INFO("Config module shutting down");
ConfigManager::instance().shutdown();
setInitialized(false);
}
} // namespace extra2d

View File

@ -0,0 +1,80 @@
#include <extra2d/modules/input_module.h>
#include <extra2d/core/service_locator.h>
#include <extra2d/services/event_service.h>
#include <extra2d/utils/logger.h>
#include "../platform/backends/sdl2/sdl2_input.h"
namespace extra2d {
InputModule::InputModule()
: Module()
, window_(nullptr)
, input_(nullptr) {
}
InputModule::~InputModule() {
if (isInitialized()) {
destroyModule();
}
}
void InputModule::setupModule() {
if (isInitialized()) {
return;
}
if (!window_) {
E2D_LOG_ERROR("Window not set - cannot initialize input module");
return;
}
input_ = window_->input();
if (!input_) {
E2D_LOG_ERROR("Input interface not available from window");
return;
}
SDL2Input* sdl2Input = dynamic_cast<SDL2Input*>(input_);
if (sdl2Input) {
auto eventService = ServiceLocator::instance().getService<IEventService>();
if (eventService) {
sdl2Input->setEventCallback([eventService](const Event& event) {
Event mutableEvent = event;
eventService->dispatch(mutableEvent);
});
E2D_LOG_INFO("Input events connected to EventService");
} else {
E2D_LOG_WARN("EventService not available - input events will not be dispatched");
}
}
setInitialized(true);
E2D_LOG_INFO("Input module initialized");
E2D_LOG_INFO(" Deadzone: {}", config_.deadzone);
E2D_LOG_INFO(" Mouse sensitivity: {}", config_.mouseSensitivity);
E2D_LOG_INFO(" Vibration: {}", config_.enableVibration ? "enabled" : "disabled");
}
void InputModule::destroyModule() {
if (!isInitialized()) {
return;
}
E2D_LOG_INFO("Input module shutting down");
input_ = nullptr;
setInitialized(false);
}
void InputModule::onUpdate(UpdateContext& ctx) {
if (!isInitialized() || !input_) {
ctx.next();
return;
}
input_->update();
ctx.next();
}
} // namespace extra2d

View File

@ -0,0 +1,47 @@
#include <extra2d/modules/logger_module.h>
#include <extra2d/utils/logger.h>
namespace extra2d {
LoggerModule::LoggerModule()
: Module()
, logLevel_(LogLevel::Info)
, consoleOutput_(true)
, fileOutput_(false) {
}
LoggerModule::~LoggerModule() {
if (isInitialized()) {
destroyModule();
}
}
void LoggerModule::setupModule() {
if (isInitialized()) {
return;
}
Logger::init();
Logger::setLevel(logLevel_);
Logger::setConsoleOutput(consoleOutput_);
if (fileOutput_ && !logFilePath_.empty()) {
Logger::setFileOutput(logFilePath_);
}
setInitialized(true);
E2D_LOG_INFO("Logger module initialized");
}
void LoggerModule::destroyModule() {
if (!isInitialized()) {
return;
}
E2D_LOG_INFO("Logger module shutting down");
Logger::shutdown();
setInitialized(false);
}
} // namespace extra2d

View File

@ -0,0 +1,101 @@
#include <extra2d/modules/platform_module.h>
#include <extra2d/config/config_manager.h>
#include <extra2d/utils/logger.h>
#ifdef __SWITCH__
#include <switch.h>
#endif
namespace extra2d {
PlatformModule::PlatformModule()
: Module()
, targetPlatform_(PlatformType::Auto)
, resolvedPlatform_(PlatformType::Windows) {
}
PlatformModule::~PlatformModule() {
if (isInitialized()) {
destroyModule();
}
}
void PlatformModule::setupModule() {
if (isInitialized()) {
return;
}
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;
}
if (resolvedPlatform_ == PlatformType::Switch) {
if (!initSwitch()) {
return;
}
}
setInitialized(true);
E2D_LOG_INFO("Platform module initialized ({})", getPlatformTypeName(resolvedPlatform_));
}
void PlatformModule::destroyModule() {
if (!isInitialized()) {
return;
}
E2D_LOG_INFO("Platform module shutting down");
if (resolvedPlatform_ == PlatformType::Switch) {
shutdownSwitch();
}
platformConfig_.reset();
setInitialized(false);
}
bool PlatformModule::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 PlatformModule::shutdownSwitch() {
#ifdef __SWITCH__
romfsExit();
socketExit();
#endif
}
} // namespace extra2d

View File

@ -0,0 +1,98 @@
#include <extra2d/modules/render_module.h>
#include <extra2d/graphics/opengl/gl_shader.h>
#include <extra2d/graphics/shader_manager.h>
#include <extra2d/platform/iwindow.h>
#include <extra2d/utils/logger.h>
#include <algorithm>
namespace extra2d {
RenderModule::RenderModule()
: Module()
, window_(nullptr)
, renderer_(nullptr) {
}
RenderModule::~RenderModule() {
if (isInitialized()) {
destroyModule();
}
}
void RenderModule::setupModule() {
if (isInitialized()) {
return;
}
if (!config_.validate()) {
E2D_LOG_ERROR("Invalid render config");
return;
}
if (!window_) {
E2D_LOG_ERROR("Render module requires window to be set");
return;
}
auto shaderFactory = std::make_shared<GLShaderFactory>();
if (!ShaderManager::getInstance().init(shaderFactory, "extra2d")) {
E2D_LOG_WARN("Failed to initialize ShaderManager with default paths");
}
if (!ShaderManager::getInstance().loadBuiltinShaders()) {
E2D_LOG_WARN("Failed to load some builtin shaders");
}
renderer_ = RenderBackend::create(config_.backend);
if (!renderer_) {
E2D_LOG_ERROR("Failed to create render backend");
return;
}
if (!renderer_->init(window_)) {
E2D_LOG_ERROR("Failed to initialize renderer");
renderer_.reset();
return;
}
setInitialized(true);
E2D_LOG_INFO("Render module initialized");
}
void RenderModule::destroyModule() {
if (!isInitialized()) {
return;
}
if (renderer_) {
renderer_->shutdown();
renderer_.reset();
}
ShaderManager::getInstance().shutdown();
setInitialized(false);
E2D_LOG_INFO("Render module shutdown");
}
void RenderModule::beforeRender(RenderContext& ctx) {
if (!isInitialized() || !renderer_) {
ctx.next();
return;
}
renderer_->beginFrame(Color(0.0f, 0.0f, 0.0f, 1.0f));
ctx.next();
}
void RenderModule::afterRender(RenderContext& ctx) {
if (!isInitialized() || !window_) {
ctx.next();
return;
}
window_->swap();
ctx.next();
}
} // namespace extra2d

View File

@ -0,0 +1,121 @@
#include <extra2d/modules/window_module.h>
#include <extra2d/platform/platform_module.h>
#include <extra2d/utils/logger.h>
#include <SDL.h>
#ifdef __SWITCH__
#include <switch.h>
#endif
namespace extra2d {
WindowModule::WindowModule()
: Module()
, sdl2Initialized_(false) {
}
WindowModule::~WindowModule() {
if (isInitialized()) {
destroyModule();
}
}
void WindowModule::setupModule() {
if (isInitialized()) {
return;
}
#ifdef __SWITCH__
windowConfig_.mode = WindowMode::Fullscreen;
windowConfig_.resizable = false;
windowConfig_.highDPI = false;
E2D_LOG_INFO("Switch platform: forcing fullscreen mode");
#endif
if (!initSDL2()) {
return;
}
extern void initSDL2Backend();
initSDL2Backend();
if (!BackendFactory::has("sdl2")) {
E2D_LOG_ERROR("SDL2 backend not registered!");
shutdownSDL2();
return;
}
if (!createWindow(windowConfig_)) {
E2D_LOG_ERROR("Failed to create window");
shutdownSDL2();
return;
}
setInitialized(true);
E2D_LOG_INFO("Window module initialized");
E2D_LOG_INFO(" Window: {}x{}", window_->width(), window_->height());
E2D_LOG_INFO(" Backend: SDL2");
E2D_LOG_INFO(" VSync: {}", windowConfig_.vsync);
E2D_LOG_INFO(" Fullscreen: {}", windowConfig_.isFullscreen());
}
void WindowModule::destroyModule() {
if (!isInitialized()) {
return;
}
E2D_LOG_INFO("Window module shutting down");
if (window_) {
window_->destroy();
window_.reset();
}
shutdownSDL2();
setInitialized(false);
}
bool WindowModule::initSDL2() {
Uint32 initFlags = SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER;
#ifdef __SWITCH__
initFlags |= SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER;
#endif
if (SDL_Init(initFlags) != 0) {
E2D_LOG_ERROR("Failed to initialize SDL2: {}", SDL_GetError());
return false;
}
sdl2Initialized_ = true;
E2D_LOG_INFO("SDL2 initialized successfully");
return true;
}
void WindowModule::shutdownSDL2() {
if (!sdl2Initialized_) {
return;
}
SDL_Quit();
sdl2Initialized_ = false;
E2D_LOG_INFO("SDL2 shutdown");
}
bool WindowModule::createWindow(const WindowConfigData& config) {
window_ = BackendFactory::createWindow("sdl2");
if (!window_) {
E2D_LOG_ERROR("Failed to create SDL2 window");
return false;
}
if (!window_->create(config)) {
E2D_LOG_ERROR("Failed to create window with specified config");
return false;
}
return true;
}
} // namespace extra2d

View File

@ -1,213 +0,0 @@
#include <extra2d/platform/input_module.h>
#include <extra2d/config/module_registry.h>
#include <extra2d/core/service_locator.h>
#include <extra2d/platform/window_module.h>
#include <extra2d/services/event_service.h>
#include <extra2d/utils/logger.h>
#include <nlohmann/json.hpp>
#include "backends/sdl2/sdl2_input.h"
using json = nlohmann::json;
namespace extra2d {
static ModuleId s_inputModuleId = INVALID_MODULE_ID;
ModuleId get_input_module_id() {
return s_inputModuleId;
}
bool InputModuleConfig::validate() const {
return inputConfig.isDeadzoneValid();
}
void InputModuleConfig::applyPlatformConstraints(PlatformType platform) {
#ifdef __SWITCH__
(void)platform;
inputConfig.enableVibration = true;
inputConfig.maxGamepads = 2;
#else
(void)platform;
#endif
}
bool InputModuleConfig::loadFromJson(const void* jsonData) {
if (!jsonData) return false;
try {
const json& j = *static_cast<const json*>(jsonData);
if (j.contains("enabled")) {
inputConfig.enabled = j["enabled"].get<bool>();
}
if (j.contains("rawMouseInput")) {
inputConfig.rawMouseInput = j["rawMouseInput"].get<bool>();
}
if (j.contains("mouseSensitivity")) {
inputConfig.mouseSensitivity = j["mouseSensitivity"].get<float>();
}
if (j.contains("invertMouseY")) {
inputConfig.invertMouseY = j["invertMouseY"].get<bool>();
}
if (j.contains("invertMouseX")) {
inputConfig.invertMouseX = j["invertMouseX"].get<bool>();
}
if (j.contains("deadzone")) {
inputConfig.deadzone = j["deadzone"].get<float>();
}
if (j.contains("triggerThreshold")) {
inputConfig.triggerThreshold = j["triggerThreshold"].get<float>();
}
if (j.contains("enableVibration")) {
inputConfig.enableVibration = j["enableVibration"].get<bool>();
}
if (j.contains("maxGamepads")) {
inputConfig.maxGamepads = j["maxGamepads"].get<int>();
}
if (j.contains("autoConnectGamepads")) {
inputConfig.autoConnectGamepads = j["autoConnectGamepads"].get<bool>();
}
if (j.contains("gamepadMappingFile")) {
inputConfig.gamepadMappingFile = j["gamepadMappingFile"].get<std::string>();
}
return true;
} catch (...) {
return false;
}
}
bool InputModuleConfig::saveToJson(void* jsonData) const {
if (!jsonData) return false;
try {
json& j = *static_cast<json*>(jsonData);
j["enabled"] = inputConfig.enabled;
j["rawMouseInput"] = inputConfig.rawMouseInput;
j["mouseSensitivity"] = inputConfig.mouseSensitivity;
j["invertMouseY"] = inputConfig.invertMouseY;
j["invertMouseX"] = inputConfig.invertMouseX;
j["deadzone"] = inputConfig.deadzone;
j["triggerThreshold"] = inputConfig.triggerThreshold;
j["enableVibration"] = inputConfig.enableVibration;
j["maxGamepads"] = inputConfig.maxGamepads;
j["autoConnectGamepads"] = inputConfig.autoConnectGamepads;
j["gamepadMappingFile"] = inputConfig.gamepadMappingFile;
return true;
} catch (...) {
return false;
}
}
InputModuleInitializer::InputModuleInitializer()
: moduleId_(INVALID_MODULE_ID)
, input_(nullptr)
, initialized_(false) {
}
InputModuleInitializer::~InputModuleInitializer() {
if (initialized_) {
shutdown();
}
}
std::vector<ModuleId> InputModuleInitializer::getDependencies() const {
return { get_window_module_id() };
}
bool InputModuleInitializer::initialize(const IModuleConfig* config) {
if (initialized_) return true;
const InputModuleConfig* inputConfig = dynamic_cast<const InputModuleConfig*>(config);
if (!inputConfig) {
E2D_LOG_ERROR("Invalid input module config");
return false;
}
config_ = inputConfig->inputConfig;
auto& registry = ModuleRegistry::instance();
auto* windowInitializer = registry.getInitializer(get_window_module_id());
if (!windowInitializer) {
E2D_LOG_ERROR("Window module not found - Input module depends on it");
return false;
}
auto* windowModule = static_cast<WindowModuleInitializer*>(windowInitializer);
IWindow* window = windowModule->getWindow();
if (!window) {
E2D_LOG_ERROR("Window not created - cannot get input interface");
return false;
}
input_ = window->input();
if (!input_) {
E2D_LOG_ERROR("Input interface not available from window");
return false;
}
SDL2Input* sdl2Input = dynamic_cast<SDL2Input*>(input_);
if (sdl2Input) {
auto eventService = ServiceLocator::instance().getService<IEventService>();
if (eventService) {
sdl2Input->setEventCallback([eventService](const Event& event) {
Event mutableEvent = event;
eventService->dispatch(mutableEvent);
});
E2D_LOG_INFO("Input events connected to EventService");
} else {
E2D_LOG_WARN("EventService not available - input events will not be dispatched");
}
}
initialized_ = true;
E2D_LOG_INFO("Input module initialized");
E2D_LOG_INFO(" Deadzone: {}", config_.deadzone);
E2D_LOG_INFO(" Mouse sensitivity: {}", config_.mouseSensitivity);
E2D_LOG_INFO(" Vibration: {}", config_.enableVibration ? "enabled" : "disabled");
return true;
}
void InputModuleInitializer::shutdown() {
if (!initialized_) return;
E2D_LOG_INFO("Input module shutting down");
input_ = nullptr;
initialized_ = false;
}
void InputModuleInitializer::update() {
if (!initialized_ || !input_) return;
input_->update();
}
void register_input_module() {
if (s_inputModuleId != INVALID_MODULE_ID) return;
s_inputModuleId = ModuleRegistry::instance().registerModule(
makeUnique<InputModuleConfig>(),
[]() -> UniquePtr<IModuleInitializer> {
auto initializer = makeUnique<InputModuleInitializer>();
initializer->setModuleId(s_inputModuleId);
return initializer;
}
);
}
namespace {
struct InputModuleAutoRegister {
InputModuleAutoRegister() {
register_input_module();
}
};
static InputModuleAutoRegister s_autoRegister;
}
}

View File

@ -1,168 +0,0 @@
#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;
}
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,258 +0,0 @@
#include <extra2d/platform/window_module.h>
#include <extra2d/config/module_registry.h>
#include <extra2d/platform/platform_module.h>
#include <extra2d/utils/logger.h>
#include <nlohmann/json.hpp>
#include <SDL.h>
#ifdef __SWITCH__
#include <switch.h>
#endif
using json = nlohmann::json;
namespace extra2d {
static ModuleId s_windowModuleId = INVALID_MODULE_ID;
ModuleId get_window_module_id() {
return s_windowModuleId;
}
void WindowModuleConfig::applyPlatformConstraints(PlatformType platform) {
#ifdef __SWITCH__
(void)platform;
windowConfig.mode = WindowMode::Fullscreen;
windowConfig.resizable = false;
windowConfig.highDPI = false;
windowConfig.width = 1920;
windowConfig.height = 1080;
#else
(void)platform;
#endif
}
bool WindowModuleConfig::loadFromJson(const void* jsonData) {
if (!jsonData) return false;
try {
const json& j = *static_cast<const json*>(jsonData);
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("minWidth")) {
windowConfig.minWidth = j["minWidth"].get<int>();
}
if (j.contains("minHeight")) {
windowConfig.minHeight = j["minHeight"].get<int>();
}
if (j.contains("fullscreen")) {
windowConfig.mode = j["fullscreen"].get<bool>() ? WindowMode::Fullscreen : WindowMode::Windowed;
}
if (j.contains("mode")) {
std::string modeStr = j["mode"].get<std::string>();
if (modeStr == "fullscreen") {
windowConfig.mode = WindowMode::Fullscreen;
} else if (modeStr == "borderless") {
windowConfig.mode = WindowMode::Borderless;
} else {
windowConfig.mode = WindowMode::Windowed;
}
}
if (j.contains("vsync")) {
windowConfig.vsync = j["vsync"].get<bool>();
}
if (j.contains("resizable")) {
windowConfig.resizable = j["resizable"].get<bool>();
}
if (j.contains("highDPI")) {
windowConfig.highDPI = j["highDPI"].get<bool>();
}
if (j.contains("multisamples")) {
windowConfig.multisamples = j["multisamples"].get<int>();
}
return true;
} catch (...) {
return false;
}
}
bool WindowModuleConfig::saveToJson(void* jsonData) const {
if (!jsonData) return false;
try {
json& j = *static_cast<json*>(jsonData);
j["title"] = windowConfig.title;
j["width"] = windowConfig.width;
j["height"] = windowConfig.height;
j["minWidth"] = windowConfig.minWidth;
j["minHeight"] = windowConfig.minHeight;
switch (windowConfig.mode) {
case WindowMode::Fullscreen:
j["mode"] = "fullscreen";
break;
case WindowMode::Borderless:
j["mode"] = "borderless";
break;
default:
j["mode"] = "windowed";
break;
}
j["vsync"] = windowConfig.vsync;
j["resizable"] = windowConfig.resizable;
j["highDPI"] = windowConfig.highDPI;
j["multisamples"] = windowConfig.multisamples;
return true;
} catch (...) {
return false;
}
}
WindowModuleInitializer::WindowModuleInitializer()
: moduleId_(INVALID_MODULE_ID)
, initialized_(false)
, sdl2Initialized_(false) {
}
WindowModuleInitializer::~WindowModuleInitializer() {
if (initialized_) {
shutdown();
}
}
bool WindowModuleInitializer::initSDL2() {
Uint32 initFlags = SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER;
#ifdef __SWITCH__
initFlags |= SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER;
#endif
if (SDL_Init(initFlags) != 0) {
E2D_LOG_ERROR("Failed to initialize SDL2: {}", SDL_GetError());
return false;
}
sdl2Initialized_ = true;
E2D_LOG_INFO("SDL2 initialized successfully");
return true;
}
void WindowModuleInitializer::shutdownSDL2() {
if (!sdl2Initialized_) return;
SDL_Quit();
sdl2Initialized_ = false;
E2D_LOG_INFO("SDL2 shutdown");
}
bool WindowModuleInitializer::initialize(const IModuleConfig* config) {
if (initialized_) return true;
const WindowModuleConfig* windowConfig = dynamic_cast<const WindowModuleConfig*>(config);
if (!windowConfig) {
E2D_LOG_ERROR("Invalid window module config");
return false;
}
windowConfig_ = windowConfig->windowConfig;
#ifdef __SWITCH__
windowConfig_.mode = WindowMode::Fullscreen;
windowConfig_.resizable = false;
windowConfig_.highDPI = false;
E2D_LOG_INFO("Switch platform: forcing fullscreen mode");
#endif
if (!initSDL2()) {
return false;
}
extern void initSDL2Backend();
initSDL2Backend();
if (!BackendFactory::has("sdl2")) {
E2D_LOG_ERROR("SDL2 backend not registered!");
shutdownSDL2();
return false;
}
if (!createWindow(windowConfig_)) {
E2D_LOG_ERROR("Failed to create window");
shutdownSDL2();
return false;
}
initialized_ = true;
E2D_LOG_INFO("Window module initialized");
E2D_LOG_INFO(" Window: {}x{}", window_->width(), window_->height());
E2D_LOG_INFO(" Backend: SDL2");
E2D_LOG_INFO(" VSync: {}", windowConfig_.vsync);
E2D_LOG_INFO(" Fullscreen: {}", windowConfig_.isFullscreen());
return true;
}
bool WindowModuleInitializer::createWindow(const WindowConfigData& config) {
window_ = BackendFactory::createWindow("sdl2");
if (!window_) {
E2D_LOG_ERROR("Failed to create SDL2 window");
return false;
}
if (!window_->create(config)) {
E2D_LOG_ERROR("Failed to create window with specified config");
return false;
}
return true;
}
void WindowModuleInitializer::shutdown() {
if (!initialized_) return;
E2D_LOG_INFO("Window module shutting down");
if (window_) {
window_->destroy();
window_.reset();
}
shutdownSDL2();
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;
}
}

View File

@ -3,6 +3,8 @@
#include <extra2d/graphics/render_command.h> #include <extra2d/graphics/render_command.h>
#include <extra2d/scene/node.h> #include <extra2d/scene/node.h>
#include <extra2d/scene/scene.h> #include <extra2d/scene/scene.h>
#include <extra2d/services/event_service.h>
#include <extra2d/core/service_locator.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
namespace extra2d { namespace extra2d {
@ -650,4 +652,40 @@ void Node::collectRenderCommands(std::vector<RenderCommand> &commands,
} }
} }
// ============================================================================
// 事件系统便捷方法
// ============================================================================
ListenerId Node::addListener(EventType type, EventDispatcher::EventCallback callback) {
auto eventService = ServiceLocator::instance().getService<IEventService>();
if (eventService) {
return eventService->addListener(type, std::move(callback));
}
return eventDispatcher_.addListener(type, std::move(callback));
}
void Node::removeListener(ListenerId id) {
auto eventService = ServiceLocator::instance().getService<IEventService>();
if (eventService) {
eventService->removeListener(id);
}
eventDispatcher_.removeListener(id);
}
void Node::removeAllListeners(EventType type) {
auto eventService = ServiceLocator::instance().getService<IEventService>();
if (eventService) {
eventService->removeAllListeners(type);
}
eventDispatcher_.removeAllListeners(type);
}
void Node::removeAllListeners() {
auto eventService = ServiceLocator::instance().getService<IEventService>();
if (eventService) {
eventService->removeAllListeners();
}
eventDispatcher_.removeAllListeners();
}
} // namespace extra2d } // namespace extra2d

View File

@ -1,114 +0,0 @@
#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

View File

@ -39,12 +39,11 @@
- **跨平台支持**Windows、Linux、macOS、Nintendo Switch - **跨平台支持**Windows、Linux、macOS、Nintendo Switch
- **模块化架构**:模块系统 + 服务系统,灵活可扩展 - **模块化架构**:模块系统 + 服务系统,灵活可扩展
- **显式注册**:通过 `Application::use()` 注册模块,参考 Kiwano 设计
- **场景图系统**:树形节点结构,支持变换继承 - **场景图系统**:树形节点结构,支持变换继承
- **输入系统**:键盘、鼠标、手柄、触摸,事件驱动 - **输入系统**:键盘、鼠标、手柄、触摸,事件驱动
- **渲染系统**OpenGL ES 3.2,支持自定义着色器 - **渲染系统**OpenGL ES 3.2,支持自定义着色器
- **视口适配**:多种适配模式,自动响应窗口大小变化 - **视口适配**:多种适配模式,自动响应窗口大小变化
- **音频系统**:高质量音频播放(规划中)
- **UI 系统**:完整的 UI 控件支持(规划中)
--- ---
@ -59,18 +58,19 @@ flowchart TB
subgraph Core["Core Layer (核心层)"] subgraph Core["Core Layer (核心层)"]
direction LR direction LR
MR[ModuleRegistry<br/>模块注册表] MOD[Module<br/>模块基类]
SL[ServiceLocator<br/>服务定位器] SL[ServiceLocator<br/>服务定位器]
end end
subgraph Modules["Modules (模块系统)"] subgraph Modules["Modules (模块系统)"]
direction TB direction TB
subgraph CoreModules["Core Modules"] subgraph CoreModules["Core Modules"]
LOGGER[Logger Module<br/>日志系统]
CONFIG[Config Module<br/>配置管理] CONFIG[Config Module<br/>配置管理]
PLATFORM[Platform Module<br/>平台检测] PLATFORM[Platform Module<br/>平台检测]
WINDOW[Window Module<br/>窗口管理] WINDOW[Window Module<br/>窗口管理]
end end
subgraph OtherModules["Feature Modules"] subgraph FeatureModules["Feature Modules"]
INPUT[Input Module<br/>输入处理] INPUT[Input Module<br/>输入处理]
RENDER[Render Module<br/>渲染系统] RENDER[Render Module<br/>渲染系统]
end end
@ -106,9 +106,9 @@ flowchart TB
INPUT_SYS[Input System<br/>输入系统] INPUT_SYS[Input System<br/>输入系统]
end end
APP --> MR APP --> MOD
APP --> SL APP --> SL
MR --> Modules MOD --> Modules
SL --> Services SL --> Services
SCENE_SVC --> SCENE SCENE_SVC --> SCENE
SCENE --> NODE SCENE --> NODE
@ -127,11 +127,12 @@ flowchart TB
| 模块 | 职责 | 优先级 | | 模块 | 职责 | 优先级 |
|-----|------|-------| |-----|------|-------|
| Config | 配置管理 | Core (0) | | Logger | 日志系统 | -1 |
| Platform | 平台检测 | Core (0) | | Config | 配置管理 | 0 |
| Window | 窗口管理 | Core (0) | | Platform | 平台检测 | 10 |
| Input | 输入处理 | Input (50) | | Window | 窗口管理 | 20 |
| Render | 渲染系统 | Graphics (100) | | Input | 输入处理 | 30 |
| Render | 渲染系统 | 40 |
### 服务系统 ### 服务系统
@ -213,8 +214,10 @@ int main() {
config.appName = "My Game"; config.appName = "My Game";
config.appVersion = "1.0.0"; config.appVersion = "1.0.0";
// 初始化 // 获取应用实例
Application& app = Application::get(); Application& app = Application::get();
// 初始化
if (!app.init(config)) { if (!app.init(config)) {
return -1; return -1;
} }
@ -244,6 +247,46 @@ int main() {
} }
``` ```
### 创建自定义模块
```cpp
#include <extra2d/core/module.h>
class MyModule : public extra2d::Module {
public:
const char* getName() const override { return "MyModule"; }
int getPriority() const override { return 1000; }
void setupModule() override {
// 初始化资源
extra2d::E2D_LOG_INFO("MyModule initialized");
}
void destroyModule() override {
// 清理资源
extra2d::E2D_LOG_INFO("MyModule destroyed");
}
void onUpdate(extra2d::UpdateContext& ctx) override {
// 更新逻辑
ctx.next(); // 继续下一个模块
}
};
// 在 main 中注册
int main() {
auto& app = extra2d::Application::get();
MyModule myModule;
app.use(myModule); // 显式注册
app.init();
app.run();
return 0;
}
```
### 场景图示例 ### 场景图示例
```cpp ```cpp
@ -300,11 +343,13 @@ eventService->addListener(EventType::GamepadButtonPressed, [](Event& e) {
| 示例 | 说明 | | 示例 | 说明 |
|-----|------| |-----|------|
| `demo_basic` | 基础示例:场景图、输入事件、视口适配 | | `demo_basic` | 基础示例:场景图、输入事件、视口适配 |
| `demo_hello_module` | 自定义模块示例:展示如何创建和注册模块 |
运行示例: 运行示例:
```bash ```bash
xmake run demo_basic xmake run demo_basic
xmake run demo_hello_module
``` ```
--- ---
@ -339,46 +384,36 @@ Extra2D/
│ │ ├── KHR/ # KHR 平台头文件 │ │ ├── KHR/ # KHR 平台头文件
│ │ ├── extra2d/ # 引擎公共头文件 │ │ ├── extra2d/ # 引擎公共头文件
│ │ │ ├── app/ # 应用程序 │ │ │ ├── app/ # 应用程序
│ │ │ ├── audio/ # 音频配置
│ │ │ ├── config/ # 配置系统 │ │ │ ├── config/ # 配置系统
│ │ │ ├── core/ # 核心类型 │ │ │ ├── core/ # 核心类型、模块基类
│ │ │ ├── debug/ # 调试配置
│ │ │ ├── event/ # 事件系统 │ │ │ ├── event/ # 事件系统
│ │ │ ├── graphics/ # 图形渲染 │ │ │ ├── graphics/ # 图形渲染
│ │ │ │ └── opengl/ # OpenGL 实现
│ │ │ ├── input/ # 输入配置 │ │ │ ├── input/ # 输入配置
│ │ │ ├── modules/ # 内置模块
│ │ │ ├── platform/ # 平台抽象 │ │ │ ├── platform/ # 平台抽象
│ │ │ ├── resource/ # 资源配置
│ │ │ ├── scene/ # 场景系统 │ │ │ ├── scene/ # 场景系统
│ │ │ ├── script/ # 脚本系统
│ │ │ ├── services/ # 服务接口 │ │ │ ├── services/ # 服务接口
│ │ │ └── utils/ # 工具库 │ │ │ └── utils/ # 工具库
│ │ ├── glad/ # OpenGL 加载器 │ │ ├── glad/ # OpenGL 加载器
│ │ └── stb/ # STB 单文件库 │ │ └── stb/ # STB 单文件库
│ ├── shaders/ # 着色器文件 │ ├── shaders/ # 着色器文件
│ │ ├── builtin/ # 内置着色器
│ │ ├── common/ # 公共着色器代码
│ │ └── effects/ # 特效着色器
│ └── src/ # 源文件 │ └── src/ # 源文件
│ ├── app/ # 应用实现 │ ├── app/ # 应用实现
│ ├── config/ # 配置实现
│ ├── core/ # 核心实现 │ ├── core/ # 核心实现
│ ├── debug/ # 调试实现
│ ├── event/ # 事件实现 │ ├── event/ # 事件实现
│ ├── glad/ # GLAD 实现
│ ├── graphics/ # 图形实现 │ ├── graphics/ # 图形实现
│ └── opengl/ # OpenGL 实现 ├── modules/ # 模块实现
│ ├── platform/ # 平台实现 │ ├── platform/ # 平台实现
│ │ └── backends/ # 后端实现
│ │ └── sdl2/ # SDL2 后端
│ ├── resource/ # 资源实现
│ ├── scene/ # 场景实现 │ ├── scene/ # 场景实现
│ ├── script/ # 脚本实现
│ ├── services/ # 服务实现 │ ├── services/ # 服务实现
│ └── utils/ # 工具实现 │ └── utils/ # 工具实现
├── docs/ # 文档 ├── docs/ # 文档
├── examples/ # 示例程序 ├── examples/ # 示例程序
│ └── basic/ # 基础示例 │ ├── basic/ # 基础示例
│ └── hello_module/ # 自定义模块示例
└── xmake/ # 构建配置 └── xmake/ # 构建配置
└── toolchains/ # 工具链配置
``` ```
--- ---

File diff suppressed because it is too large Load Diff

View File

@ -1,183 +1,79 @@
#include "hello_module.h" #include "hello_module.h"
#include <extra2d/config/module_registry.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
namespace extra2d { namespace extra2d {
static ModuleId s_helloModuleId = INVALID_MODULE_ID; HelloModule::HelloModule()
: Module()
/** , config_()
* @brief Hello模块标识符 , time_(0.0f) {
*/
ModuleId get_hello_module_id() {
return s_helloModuleId;
} }
/** HelloModule::~HelloModule() {
* @brief JSON加载配置 if (isInitialized()) {
*/ destroyModule();
bool HelloModuleConfig::loadFromJson(const void* jsonData) {
if (!jsonData) return false;
try {
const json& j = *static_cast<const json*>(jsonData);
if (j.contains("greeting")) {
config.greeting = j["greeting"].get<std::string>();
}
if (j.contains("repeatCount")) {
config.repeatCount = j["repeatCount"].get<int>();
}
if (j.contains("enableLogging")) {
config.enableLogging = j["enableLogging"].get<bool>();
}
return true;
} catch (...) {
return false;
} }
} }
/** void HelloModule::setupModule() {
* @brief JSON if (isInitialized()) {
*/ return;
bool HelloModuleConfig::saveToJson(void* jsonData) const {
if (!jsonData) return false;
try {
json& j = *static_cast<json*>(jsonData);
j["greeting"] = config.greeting;
j["repeatCount"] = config.repeatCount;
j["enableLogging"] = config.enableLogging;
return true;
} catch (...) {
return false;
} }
}
/** if (config_.greeting.empty()) {
* @brief config_.greeting = "Hello, Extra2D!";
*/ }
HelloModuleInitializer::HelloModuleInitializer()
: moduleId_(INVALID_MODULE_ID)
, initialized_(false) {
}
/** if (config_.repeatCount <= 0) {
* @brief config_.repeatCount = 1;
*/
HelloModuleInitializer::~HelloModuleInitializer() {
if (initialized_) {
shutdown();
} }
}
/** setInitialized(true);
* @brief
*/
std::vector<ModuleId> HelloModuleInitializer::getDependencies() const {
return {};
}
/**
* @brief
*/
bool HelloModuleInitializer::initialize(const IModuleConfig* config) {
if (initialized_) {
E2D_LOG_WARN("HelloModule already initialized");
return true;
}
if (!config) {
E2D_LOG_ERROR("HelloModule config is null");
return false;
}
const HelloModuleConfig* helloConfig = dynamic_cast<const HelloModuleConfig*>(config);
if (!helloConfig) {
E2D_LOG_ERROR("Invalid HelloModule config type");
return false;
}
if (!helloConfig->validate()) {
E2D_LOG_ERROR("HelloModule config validation failed");
return false;
}
config_ = helloConfig->config;
initialized_ = true;
E2D_LOG_INFO("HelloModule initialized"); E2D_LOG_INFO("HelloModule initialized");
E2D_LOG_INFO(" Greeting: {}", config_.greeting); E2D_LOG_INFO(" Greeting: {}", config_.greeting);
E2D_LOG_INFO(" Repeat Count: {}", config_.repeatCount); E2D_LOG_INFO(" Repeat Count: {}", config_.repeatCount);
E2D_LOG_INFO(" Logging Enabled: {}", config_.enableLogging); E2D_LOG_INFO(" Logging Enabled: {}", config_.enableLogging);
sayHello(); sayHello();
return true;
} }
/** void HelloModule::destroyModule() {
* @brief if (!isInitialized()) {
*/ return;
void HelloModuleInitializer::shutdown() { }
if (!initialized_) return;
if (config_.enableLogging) { if (config_.enableLogging) {
E2D_LOG_INFO("HelloModule shutdown - Goodbye!"); E2D_LOG_INFO("HelloModule shutdown - Goodbye!");
} }
initialized_ = false; setInitialized(false);
} }
/** void HelloModule::onUpdate(UpdateContext& ctx) {
* @brief if (!isInitialized()) {
*/ ctx.next();
void HelloModuleInitializer::sayHello() const { return;
if (!config_.enableLogging) return; }
time_ += ctx.getDeltaTime();
if (time_ >= 5.0f) {
sayHello();
time_ = 0.0f;
}
ctx.next();
}
void HelloModule::sayHello() const {
if (!config_.enableLogging) {
return;
}
for (int i = 0; i < config_.repeatCount; ++i) { for (int i = 0; i < config_.repeatCount; ++i) {
E2D_LOG_INFO("[HelloModule] {}", config_.greeting); E2D_LOG_INFO("[HelloModule] {}", config_.greeting);
} }
} }
/**
* @brief Hello模块
*/
void register_hello_module() {
if (s_helloModuleId != INVALID_MODULE_ID) {
E2D_LOG_WARN("HelloModule already registered");
return;
}
s_helloModuleId = ModuleRegistry::instance().registerModule(
makeUnique<HelloModuleConfig>(),
[]() -> UniquePtr<IModuleInitializer> {
auto initializer = makeUnique<HelloModuleInitializer>();
initializer->setModuleId(s_helloModuleId);
return initializer;
}
);
E2D_LOG_DEBUG("HelloModule registered with id: {}", s_helloModuleId);
}
namespace {
/**
* @brief
*
*/
struct HelloModuleAutoRegister {
HelloModuleAutoRegister() {
register_hello_module();
}
};
static HelloModuleAutoRegister s_autoRegister;
}
} // namespace extra2d } // namespace extra2d

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
#include <extra2d/config/module_config.h> #include <extra2d/core/module.h>
#include <extra2d/config/module_initializer.h>
#include <string> #include <string>
namespace extra2d { namespace extra2d {
@ -16,113 +15,54 @@ struct HelloModuleConfigData {
}; };
/** /**
* @brief Hello模块配置类 * @brief Hello模块
* *
* *
* 1. * 1. Module
* 2. IModuleConfig接口 * 2.
* 3. JSON配置加载/ * 3. 使 Application::use()
*/ */
class HelloModuleConfig : public IModuleConfig { class HelloModule : public Module {
public: public:
HelloModuleConfigData config; /**
* @brief
*/
HelloModule();
/** /**
* @brief * @brief
*/ */
ModuleInfo getModuleInfo() const override { ~HelloModule() override;
ModuleInfo info;
info.id = 0;
info.name = "HelloModule";
info.version = "1.0.0";
info.priority = ModulePriority::User;
info.enabled = true;
return info;
}
/** /**
* @brief * @brief
*/ */
std::string getConfigSectionName() const override { const char* getName() const override { return "HelloModule"; }
return "hello";
}
/**
* @brief
*/
bool validate() const override {
return !config.greeting.empty() && config.repeatCount > 0;
}
/**
* @brief
*/
void resetToDefaults() override {
config = HelloModuleConfigData{};
}
/**
* @brief
*/
void applyPlatformConstraints(PlatformType platform) override {
(void)platform;
}
/**
* @brief JSON加载配置
*/
bool loadFromJson(const void* jsonData) override;
/**
* @brief JSON
*/
bool saveToJson(void* jsonData) const override;
};
/**
* @brief Hello模块初始化器
*
*
*/
class HelloModuleInitializer : public IModuleInitializer {
public:
HelloModuleInitializer();
~HelloModuleInitializer() override;
/**
* @brief
*/
ModuleId getModuleId() const override { return moduleId_; }
/** /**
* @brief * @brief
*/ */
ModulePriority getPriority() const override { return ModulePriority::User; } int getPriority() const override { return 1000; }
/** /**
* @brief * @brief
*/ */
std::vector<ModuleId> getDependencies() const override; void setupModule() override;
/** /**
* @brief * @brief
*/ */
bool initialize(const IModuleConfig* config) override; void destroyModule() override;
/** /**
* @brief * @brief
*/ */
void shutdown() override; void onUpdate(UpdateContext& ctx) override;
/** /**
* @brief * @brief
*/ */
bool isInitialized() const override { return initialized_; } void setConfig(const HelloModuleConfigData& config) { config_ = config; }
/**
* @brief
*/
void setModuleId(ModuleId id) { moduleId_ = id; }
/** /**
* @brief * @brief
@ -130,19 +70,8 @@ public:
void sayHello() const; void sayHello() const;
private: private:
ModuleId moduleId_ = INVALID_MODULE_ID;
bool initialized_ = false;
HelloModuleConfigData config_; HelloModuleConfigData config_;
float time_ = 0.0f;
}; };
/**
* @brief Hello模块标识符
*/
ModuleId get_hello_module_id();
/**
* @brief Hello模块
*/
void register_hello_module();
} // namespace extra2d } // namespace extra2d

View File

@ -1,6 +1,5 @@
#include "hello_module.h" #include "hello_module.h"
#include <extra2d/app/application.h> #include <extra2d/app/application.h>
#include <extra2d/config/module_registry.h>
#include <extra2d/scene/scene.h> #include <extra2d/scene/scene.h>
#include <extra2d/services/scene_service.h> #include <extra2d/services/scene_service.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
@ -21,54 +20,21 @@ public:
E2D_LOG_INFO("HelloScene entered"); E2D_LOG_INFO("HelloScene entered");
setBackgroundColor(Color(0.1f, 0.1f, 0.2f, 1.0f)); setBackgroundColor(Color(0.1f, 0.1f, 0.2f, 1.0f));
ModuleId helloId = get_hello_module_id();
auto *initializer = ModuleRegistry::instance().getInitializer(helloId);
if (initializer) {
auto *helloInit = dynamic_cast<HelloModuleInitializer *>(initializer);
if (helloInit) {
E2D_LOG_INFO("Scene calling HelloModule from onEnter...");
helloInit->sayHello();
}
}
}
void onUpdate(float dt) override {
Scene::onUpdate(dt);
time_ += dt;
if (time_ >= 5.0f) {
ModuleId helloId = get_hello_module_id();
auto *initializer = ModuleRegistry::instance().getInitializer(helloId);
if (initializer) {
auto *helloInit = dynamic_cast<HelloModuleInitializer *>(initializer);
if (helloInit) {
E2D_LOG_INFO("Scene calling HelloModule from onUpdate...");
helloInit->sayHello();
}
}
time_ = 0.0f;
}
} }
private: private:
float time_ = 0.0f;
}; };
/** /**
* @brief * @brief
*/ */
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
E2D_LOG_INFO("=== Hello Module Example ===");
E2D_LOG_INFO("This example demonstrates how to create a custom module");
E2D_LOG_INFO("");
Application &app = Application::get(); Application &app = Application::get();
HelloModule helloModule;
app.use(helloModule);
AppConfig appConfig; AppConfig appConfig;
appConfig.appName = "HelloModule Example"; appConfig.appName = "HelloModule Example";
appConfig.appVersion = "1.0.0"; appConfig.appVersion = "1.0.0";
@ -78,24 +44,12 @@ int main(int argc, char *argv[]) {
return 1; return 1;
} }
E2D_LOG_INFO("");
E2D_LOG_INFO("Application initialized successfully");
E2D_LOG_INFO("HelloModule should have been auto-registered and initialized");
E2D_LOG_INFO("");
auto scene = HelloScene::create(); auto scene = HelloScene::create();
app.enterScene(scene); app.enterScene(scene);
E2D_LOG_INFO("Starting main loop...");
E2D_LOG_INFO("Press ESC or close window to exit");
E2D_LOG_INFO("");
app.run(); app.run();
E2D_LOG_INFO("Application shutting down...");
app.shutdown(); app.shutdown();
E2D_LOG_INFO("Application shutdown complete");
return 0; return 0;
} }